AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Self-driving agent loop for Claude Code. It turns an issue into a planned, verified, committed change, and lets you run, supervise, and chain workplans from the shell.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A self-driving agent loop that turns issue descriptions into working, verified code.
Wiggum wraps Claude Code in a structured orchestration loop. You give it issue files or spec documents; it produces a workplan, implements it step by step, verifies each step against your project's own toolchain, self-heals failures, and commits the results. The human decides what to build. Wiggum figures out how and keeps going until it's done.
benman1.github.io/wiggum is the one-page overview — what a run does, how it verifies, how to install. This README is the full reference.README 전문 보기 ↗A self-driving agent loop that turns issue descriptions into working, verified code.
Wiggum wraps Claude Code in a structured orchestration loop. You give it issue files or spec documents; it produces a workplan, implements it step by step, verifies each step against your project's own toolchain, self-heals failures, and commits the results. The human decides what to build. Wiggum figures out how and keeps going until it's done.
benman1.github.io/wiggum is the one-page overview — what a run does, how it verifies, how to install. This README is the full reference.
Built driving real work on Graph-of-Skills, an MCP server that helps coding agents find the right skill in a growing library instead of guessing from a name.
Kick off as many runs as you like and see all of them at once with wiggum top — every run on the machine, whichever project it was started from:
$ wiggum top
PLAN PID STATE ACTIVITY RSS CPU TASKS
/Users/me/other-project/docs/etl_plan.md 8123 running 12s 1.2G 94.0% 3/9 done, 6 left
docs/global-score_plan.md 519 running 1m 4s 497.1M 89.4% 4/12 done, 8 left
docs/api-rate-limit_plan.md - finished: complete 3d 2h - - 9/9 done
docs/onboarding-ui_plan.md 621 running (blocked) 41m 12s 288.4M 0.2% 2/7 done, 5 left
load 6.1 / 4 cores · swap 2.6G of 4.0G (65%) · 3 runs active
AI coding assistants are powerful in single-turn interactions, but real implementation work is multi-step: read the spec, write code, run the linter, fix type errors, run tests, fix regressions, commit, repeat. Each of those handoffs is a place where momentum stalls.
Wiggum automates the full cycle. It acts as a project driver that:
ruff --fix or eslint --fix), it runs the autofix first and only escalates to Claude if the issue persists.The result: you kick off a run, walk away, and come back to a branch with a series of clean commits, a reconciled plan, and a summary of what happened.
Wiggum's commands map to the natural workflow of software development — init, plan, explain, execute, check, docs, and run — with status, watch, kill, top, and chain layered on top to supervise long or batched runs.
wiggum init [preset] [--yes]
Generates a .wiggumrc configuration file for the current project. If no preset is specified, wiggum inspects the directory for known project files (package.json, next.config.ts, pyproject.toml, etc.) and picks the matching preset automatically. If a .wiggumrc already exists, it asks before overwriting.
--yes (-y) answers every one of those questions with yes, which is what makes init runnable unattended — from a setup script, a Dockerfile, or cron. Without it and with nothing on stdin to answer, init says so and exits non-zero rather than reporting a setup it did not do. A piped answer counts as an answer, so echo n | wiggum init still declines.
The generated config sets permission_mode = auto, so Claude's auto-mode classifier gates each action and an unattended run keeps a guardrail. Change the line, or pass --permission-mode, if you want something else.
Init also writes two Claude Code files: the /wiggum skill at .claude/skills/wiggum/SKILL.md and an always-loaded project rule at .claude/rules/wiggum.md (see Claude Code skill). It never edits your CLAUDE.md — it only mentions it if you don't have one.
This is the recommended first step when adopting wiggum in a new project. The generated config provides sensible defaults for verification commands that you can then tune to your specific setup.
wiggum plan <issue-files...>
wiggum plan < description.txt
echo "description" | wiggum plan
wiggum plan <issue-files...> | wiggum execute
Reads issue descriptions, specs, or requirements documents and produces a structured workplan. The plan is a markdown document with:
## Expected benefits opening: what the work is for, ranked, each with the observable signal that would show it landed after shipping. Every phase then names the benefits it serves, so a phase that serves none stands out as scope creep## Critical path derived backwards from those signals: for each one, what must be true for it to fire, asked repeatedly until the answer is something the repo already has. Where several chains come out of that, the critical path is the longest — CPM's definition, and the point of it: only the longest sets the finish. Its complement is the ### Float, the work that can slip or run alongside without moving the finish, and the section closes by naming the one task to start first. Order only — no durations or deadlines, because a planner asked for a day estimate invents one[ ] checkbox (GitHub-flavored -, *, or + bullets all count)## Open decisions section and a ## How this reaches users section, both added by the feedback pass belowOnce the plan is written, a second pass reviews it and updates the file in place, adding the context a reader needs to judge it: the choices still genuinely open (options, trade-offs, rough effort), and which documentation, help text or web pages a user would have to read to learn the feature exists — adding a real task for any that the plan missed. It is deliberately additive and will not reword, reorder or delete existing tasks. This is the same analysis wiggum explain performs on demand. Pass --no-feedback to skip it; a piped plan skips it automatically, since it is heading straight into execute where nobody reads it.
Not every task is a code edit. Where the plan depends on something not yet known, the unknown becomes its own research task, placed ahead of the work that needs it and accepted on a written artifact (findings in a file, a measured number, a recorded decision) rather than on "understand X". Where a sub-problem is large enough to be its own workplan, a task can delegate it to a bounded, nested wiggum plan + wiggum execute run instead of inlining it.
Output destination depends on how plan is invoked:
| Invocation | Plan goes to |
|---|---|
| File arg, TTY stdout (interactive) | <basename>_plan.md on disk |
File arg, piped stdout (... | wiggum execute, > file) | stdout |
| Stdin, TTY stdout | stdout |
| Stdin, piped stdout | stdout |
--plan-file <path> (any invocation) | the specified path |
In short: if stdout is not a terminal, the plan streams through stdout; otherwise it's written to <basename>_plan.md. Pass --plan-file to force a specific on-disk path.
This is a read-only analysis step. It does not modify your codebase. Before finalizing, Wiggum confirms the libraries and APIs the plan depends on actually exist, so execution doesn't start from a hallucinated assumption.
Plans use three checkbox states:
[ ] -- pending. Wiggum will pick this up on the next iteration.[x] -- done. Terminal.[~] -- dropped. Terminal. The work was intentionally abandoned mid-execution -- typically because the task turned out to be inapplicable, redundant, or out of scope once the surrounding implementation came into focus. Wiggum treats [~] exactly like [x] for control-flow purposes: it is not counted as remaining, the phase-2 loop will not re-pick it, and phase-1 reconcile will not convert it back to [ ].Record the rationale on the same line as the marker so future readers (and future iterations) understand why the task was dropped:
- [~] **2.6** -- surprisal/burstiness. Dropped: llm-server has no perplexity endpoint.
- [~] **3.1** -- cross-document coreference. Dropped: covered by upstream embeddings already.
What [~] is not:
[ ].[ ] and note the blocker in the line.[~] is a recorded decision in the plan, not a control knob.GitHub-rendering caveat: [~] renders as plain text in GitHub's task-list view (only [ ] and [x] get the interactive checkbox treatment). This is acceptable because plans are primarily read in IDEs, cat, and grep -- the marker stays distinct everywhere it matters.
wiggum explain <plan-or-issue-files...> [--explain-file <path>]
Read-only. Answers the questions a plan doesn't answer about itself, under four headings:
| Heading | What it answers |
|---|---|
| What it contains | The phases and what each actually changes, in plain terms |
| What it is worth | What becomes true when it's done — and what that's worth to a user, in their words |
| How it reaches users | Which README sections, --help text, release notes or web pages would have to change for anyone to discover it, and whether the plan already covers them |
| Open decisions | The choices still genuinely open: context, the realistic options, what each buys and costs, and rough effort |
It changes nothing — no edits, no plan, no commit. Output goes to stdout so it
can be piped or read; --explain-file <path> writes it to a file instead.
wiggum plan folds the same analysis into the plans it writes (below). explain
exists because the questions outlive the plan's creation: a plan somebody else
wrote, one already half-executed, or an issue nobody has planned yet all raise
them, and regenerating the plan to find out is both expensive and destructive.
wiggum explain docs/auth_plan.md
wiggum explain issues/*.md --explain-file docs/auth_explained.md
wiggum execute <plan-files...>
wiggum execute < plan.md
wiggum plan issue.md | wiggum execute
wiggum execute docs/plan.md --background # run detached; supervise with status/watch/kill
Takes a workplan (typically one produced by plan mode, but any structured markdown will do) and implements it through three phases. When no files are given, reads the plan from stdin. At the end, the completed plan and summary are saved with descriptive filenames derived from the plan content (e.g., docs/improve-chunking_plan.md and docs/improve-chunking_summary.md).
By default execute runs in the foreground and blocks until it finishes. Add --background (-b) to run detached and supervise the run with status, watch, and kill — see Background runs & supervision.
Phase 1 -- Diagnostic & Status Sync
Before writing any code, Wiggum compares the plan against the actual state of the repository. It marks tasks that are already complete, flags inaccuracies, and identifies what to do next. It also normalizes the plan so every task is a trackable Markdown checkbox — if a task was written as a heading, bold text, or prose, it's rewritten as a - [ ] line so wiggum's progress counting can see it. Because progress is counted after this phase, any checkboxes added here are picked up immediately. This sync is committed separately so implementation commits stay clean.
Phase 2 -- Iterative Implementation
For each iteration (up to --max-iterations or the config file value, stopping early if all tasks are done or progress stalls):
ruff --fix), then re-check. Only if the fix didn't resolve it does Claude get involved.max_validation_retries times before moving on.[ ] boxes in the plan. If none remain, execution stops early (all done). If the count didn't decrease for 2 consecutive iterations, execution stops (stalled). Otherwise, the next iteration begins.Phase 3 -- Summary & Alignment
After all iterations complete (or early stop), Wiggum:
[x]ISSUES.md, TODO.md, a docs/issues*.md, a CHANGELOG entry, a status table in the plan's own issue file). Entries the run actually finished are marked shipped with their commit refs and the observed result; entries whose tasks are still [ ] or [~] stay open, with the reason in the summary. Wiggum will not invent a ledger the repo doesn't keep, backfill an entry for untracked work, or close a remote tracker (GitHub, Jira) on its own — it names those in the summary for a human +------------------+
| Status Sync | Phase 1
| (plan vs repo) |
+--------+---------+
|
+----------v-----------+
+--->| Implement next step | Phase 2 (up to N iterations)
| +----------+-----------+
| |
| +-----v------+
| | Verify |<--+
| +-----+------+ |
| | |
| fail | pass |
| v |
| +------+------+ |
| | Autofix / +---+
| | Claude fix | (up to max_validation_retries)
| +-------------+
| |
| +-----v------+
| | Commit |
| +-----+------+
| |
| +-------v--------+
| | Progress check |
| +---+----+---+---+
| | | |
| progress | | | all done / stalled
+-----------+ | |
| +--------+
v v
+------------------+ (early stop)
| Summary & | Phase 3
| Plan Update |
+------------------+
|
+--------v---------+
| Update docs | Phase 4 (if --update-docs)
+------------------+
wiggum docs -i <input-files...> -o <output-files...>
Updates documentation files based on input context. The -i flag specifies source material (summaries, plans, changelogs, code) and the -o flag specifies which doc files to update. Claude reads the current content of each output file and updates only the sections affected by the changes described in the input files.
This can be used standalone after a run:
wiggum docs -i docs/summary.md docs/plan.md -o README.md
Or built into the execute flow with --update-docs (see Executing a plan).
wiggum check
Runs the verification waterfall from .wiggumrc against the current codebase and asks Claude to fix any failures. This is the same validation loop that runs during execute mode, but without the implementation or commit steps.
Useful for:
wiggum check as a pre-merge check.wiggum check # run all verify/autofix steps
wiggum check --verbose # show Claude output
If all steps pass, exits 0. If any step fails after max_validation_retries fix attempts, exits non-zero.
wiggum run "first prompt" "second prompt" ...
Feeds a series of prompts to Claude Code in one continuous session. The first prompt starts a fresh session; every later prompt continues it (claude --resume … --fork-session under the hood), so Claude keeps full context from one prompt to the next. Claude's responses go to stdout; wiggum's status lines and session ids go to stderr — so wiggum run … > answers.txt captures just the answers.
Prompts come from three sources, which can be combined:
wiggum run "do X" "now do Y".-f/--prompts-file) — prompts separated by a line containing only the delimiter (default ---), so each prompt can span multiple lines.cat steps.txt | wiggum run, split the same way.wiggum run "Summarize today's git log" "Draft release notes from it"
wiggum run -f steps.txt
echo "What changed in the last commit?" | wiggum run
wiggum run --effort max "Audit this module for race conditions"
wiggum run --model sonnet "Summarize the last 20 commits"
With --session-file <path>, the session id is saved to that file and resumed on the next invocation. This lets a cron job run a step now and follow up later in the same session:
# Monday — starts a session and saves its id to .wiggum-session
wiggum run --session-file .wiggum-session "Scaffold the API skeleton"
# Tuesday — resumes Monday's session and builds on it
wiggum run --session-file .wiggum-session "Now add auth to the API you built"
The id is rewritten after every prompt, so a follow-up resumes from the latest completed step even if a later prompt failed mid-chain. Pass --new-session to ignore an existing session file and start over. Pair it with permission_mode = auto (or --permission-mode auto) so unattended runs let Claude's auto-mode classifier decide each action.
For a complete, copy-pasteable cron setup — wrapper script, environment, and the gotchas that bite unattended jobs — see Scheduling unattended runs.
wiggum execute normally runs in the foreground and blocks until it finishes. For long plans — or when you want to supervise a run, bound it, or fire off several — run it detached and drive it with the supervision commands.
wiggum execute docs/plan.md --background # or -b
--background launches the loop in a detached process, records its pid, and captures all output to sidecar files next to the plan:
docs/plan.pid — the wiggum process id for this run, and on a second line the time that process starteddocs/plan.out — the full run output (phase headers, progress, status)docs/plan.log — the structured run logEvery supervision command refers to a run by its plan file and derives those sidecars from it:
| Command | Purpose |
|---|---|
wiggum status docs/plan.md | Print task counts and run state: not started, running, running but appears blocked, or finished: <reason>. Read-only. |
wiggum watch docs/plan.md | Stream the run's output and block until it finishes — wiggum's "wait". Attaches near the end of the backlog (--tail) and names the task the plan is on. A plan whose turn in a chain has not come yet is waited for, not reported missing. Exits 0 only when the run finished complete. |
wiggum watch --chain [<plan>|<pid>] | Follow a run across plans rather than watching one: prints each plan as the chain reaches it and keeps streaming through the transitions. Name it by plan file or pid; with neither it takes the only live run, or the only one in this directory (--here to insist on that). |
wiggum kill docs/plan.md | Stop the run — and only this run's process tree (the wiggum process and the claude it spawned). Never a blanket kill. |
wiggum top | An at-a-glance overview of every run on the machine: one line per run — plan, pid, state, time since last activity, what the run is costing in RSS and CPU, and task tally — over a footer giving load, swap and the number of live runs. Read-only. --json for scripts. |
Unlike status (one plan), wiggum top surveys every run at once — and not only the ones in this directory. Any run in flight anywhere on the machine appears, shown by its absolute path when it belongs to another project:
$ wiggum top
PLAN PID STATE ACTIVITY RSS CPU TASKS
docs/ui_plan.md 621 running (blocked) 41m 12s 288.4M 0.2% 2/7 done, 5 left
/Users/me/other-project/docs/etl_plan.md 8123 running 12s 1.2G 94.0% 3/9 done, 6 left
docs/global-score_plan.md 519 running 1m 4s 497.1M 89.4% 4/12 done, 8 left
docs/api_plan.md - finished: complete 3d 2h - - 9/9 done
load 6.1 / 4 cores · swap 2.6G of 4.0G (65%) · 3 runs active
Rows are ordered by state: blocked first, then running, then scheduled, then everything finished. ACTIVITY is the age of the run's newest write, which is what separates a long task from a wedged one — both read running, and only the clock tells them apart. A live run counts its .pid as well as its .log/.out, because a run that has just claimed its sidecar has written nothing else yet; a finished run ages from its output alone, so a leftover pidfile cannot make a plan that ended in July read as active seconds ago.
RSS and CPU are the run's whole process tree, not the pid in the row: a run's own bash is about a megabyte and idle, and the cost is the claude it spawned and whatever that spawned in turn. They are an instantaneous sample, so a quiet reading is not proof a run is cheap — the run that takes a machine into swap is usually quiet by the time anyone looks. A finished run shows -.
The footer is the launch decision: load against core count, swap used, and how many runs are live. Swap is the number that matters on a small machine — past about 90% on 16 GB, builds start timing out and tests fail differently run to run, which reads as a code failure and is not. Each piece is dropped rather than guessed when the platform will not answer.
wiggum top --json emits the same records for scripts, with pid, idle_seconds, rss_kb and cpu_percent null when absent rather than -. The footer is table-only; the JSON stays a plain array of runs.
Every run registers a pidfile while it works — foreground and background alike — so a plan running inside wiggum chain appears here too, as the row for whichever plan the chain is on right now. A scheduled run appears as scheduled for <time>.
Two things are listed, and they answer different questions:
| Source | What it contributes | Scope |
|---|---|---|
The run registry (~/.wiggum/runs) | Runs that are in flight right now | The whole machine |
.pid / .scheduled sidecars | Runs recorded here, including finished ones | docs/ and the current directory |
So the default view is "everything running, plus this project's history". Pass directories, plan files, or sidecars to narrow it to one place — arguments turn the machine-wide listing off. (A run drops its own registration and pidfile when it ends, and watch/kill clear the pidfile too, so a finished foreground or chained run drops off the list; an unwatched background run lingers locally as finished: <reason>.)
Registry entries are named by pid and pruned on read, so a run that is killed outright — or a machine that reboots mid-run — leaves nothing behind to report as running.
watch can bound a run so a wedged loop can't hang forever:
wiggum watch docs/plan.md --timeout 1800 --kill-on-timeout # give up after 30 min and kill it
wiggum watch docs/plan.md --poll-interval 2 # poll for new output every 2s (default 5)
wiggum watch docs/plan.md --heartbeat 0 # no "[watch] ..." lines (default: every 60s of silence)
wiggum watch docs/plan.md --tail 0 # skip the backlog; show only what arrives from now
watch attaches near the end of what the run has written, not at the start of it.
The run separator keeps earlier runs of the same plan off the screen, but a run
going for hours has one separator, at the top — so replaying from it means pages of
scrollback before the first line that is actually new, and the wait grows with the
run. --tail bounds that (default 20 lines, 0 for none); it never reaches back
past the current run's separator however large you set it.
Both watch and watch --chain also name the task the plan is on, on attach and in
each heartbeat:
$ wiggum watch docs/page-identity_plan.md
Watching wiggum run for docs/page-identity_plan.md (pid 48548)...
task: Use the platform where it prevents a wrong finding
That line is read from the plan — the first task still open — rather than observed
from the run. Wiggum works a plan top-down, so it is what the loop is on or about to
pick up; it is not a claim about what claude is doing this second.
watch returnsA run can go many minutes without writing anything, and a stream that has printed
nothing for ten minutes is indistinguishable from a wedged watcher. After 60s of
silence watch prints one line to stderr saying what it is attached to and how the
plan is progressing, so the two are never confused:
[watch] 10m 0s attached to pid 2244, plan 3 of 6 -- 4/11 tasks done, on: Backfill the attribution table, no new output
The exit code distinguishes the ways a watch can end, and is never 0 for a watch
that waited for nothing — so wiggum watch docs/plan.md && ./deploy.sh means the
run actually finished:
| Code | Meaning |
|---|---|
0 | The run finished complete. |
1 | Nothing to watch, and no wiggum run live anywhere that could start it. |
4 | The run ended stalled, incomplete, killed, or with no recorded status. (--chain is the exception: a foreground chain records none, so it exits 0 when the process ends cleanly.) |
6 | The watch timed out; the run is still going. Says nothing about the run. |
Detecting a blocked run. status reports running but appears blocked (or a finished run as stalled) when the output shows wiggum spinning without progress — repeated No progress detected, Stalled for ..., or a verification waterfall that gave up (Validation failed N times). When that happens, read the tail of docs/plan.out / docs/plan.log to find the cause: usually a failing verify command or a task whose acceptance can't be met. Fix the plan or the source (not .wiggumrc) and re-run.
--at <WHEN> waits until a wall-clock time and then runs the plan once, detached. It is for the "start this at 1am, I'm going to bed" case — not for a recurring schedule.
wiggum execute docs/plan.md --at 01:07 # tonight at 01:07
wiggum execute docs/plan.md --at +90m # 90 minutes from now
wiggum execute docs/plan.md --at @1756180020 # at that epoch second
Three time forms, and no others:
| Form | Means | Notes |
|---|---|---|
+<N>m, +<N>h, +<N>d | now plus N minutes / hours / days | the unit is required — a bare +90 means seconds to sleep and minutes to at, and guessing between them is a sixtyfold error made silently |
HH:MM | the next time the clock reads that | rolls to tomorrow when the time has already passed today; a time equal to right now means tomorrow |
@<epoch> | that epoch second, as-is | the escape hatch for a specific calendar date |
One accepted inaccuracy on HH:MM. It resolves to "now plus the number of seconds until the clock reads that", so on the two nights a year a daylight-saving transition falls inside the wait, the run starts an hour early or late. Handling that properly needs the calendar parsing this design exists to avoid; use @<epoch> on those two nights if the hour matters.
Why no calendar-date form. Turning Aug 30 01:07 into an epoch needs date -d (GNU) or date -j -f (BSD), and neither accepts the other's syntax — so supporting it would put a platform branch in the middle of the feature. @<epoch> keeps that outside wiggum: produce the epoch with whatever your platform gives you and hand it over.
wiggum execute docs/plan.md --at "@$(date -d '2026-08-30 01:07' +%s)" # GNU date
wiggum execute docs/plan.md --at "@$(date -j -f '%Y-%m-%d %H:%M' '2026-08-30 01:07' +%s)" # BSD date
Anything else is refused before anything is scheduled, with a message naming the same three forms:
$ wiggum execute docs/plan.md --at 1am
Error: invalid --at '1am' (expected +<N>m|h|d, HH:MM or @<epoch>; e.g. +90m, 01:07, @1756180020).
A time already in the past is refused too, rather than firing immediately.
--at implies detachment. Waiting six hours in the foreground blocks the terminal for no benefit, so --at hands off exactly the way --background does — same pidfile, same .out and .log sidecars once it starts. Passing --background as well is accepted and ignored (wiggum says so, so nobody has to wonder which won).
Supervising a scheduled run. Between scheduling and firing, the run is waiting, not running, and status says so — the distinction is what tells you whether to expect output yet:
$ wiggum execute docs/plan.md --at 01:07
Scheduled wiggum execute for 01:07:00 tomorrow (in 3h 7m).
plan: docs/plan.md
pid: 41207
output: docs/plan.out
status: wiggum status docs/plan.md
kill: wiggum kill docs/plan.md
Runs once. Nothing recurring was created; wiggum will not keep this machine awake.
$ wiggum status docs/plan.md
Plan: docs/plan.md
Tasks: 0/12 done, 12 remaining, 0 dropped
State: scheduled for 01:07:00 tomorrow (in 3h 6m)
$ wiggum kill docs/plan.md
Cancelling the wiggum run scheduled for 01:07:00 tomorrow (waiter pid 41207)...
wiggum status <plan> reports scheduled for <time> (in <duration>) while it waits, then the ordinary running / finished: <reason> states once it fires. A scheduled run never reads as running.wiggum kill <plan> cancels the schedule — it says "cancelled", not "killed", because nothing ran and there is no output to go looking for. It signals only the waiter's recorded pid, never a pattern match.wiggum watch <plan> on a scheduled run waits for it: with a wiggum process live on the machine, watch blocks until the plan starts and then streams it. A --at waiter counts as that live process, so scheduling a run and watching its plan is a single sequence rather than two round trips.The pending schedule lives in one sidecar, docs/plan.scheduled, next to the plan; it is removed when the run fires or is cancelled.
If the machine sleeps. The wait polls the clock rather than sleeping the whole interval, so it self-corrects across a suspend instead of firing late by the suspend duration — but a machine that is asleep at 01:07 runs nothing at 01:07. It picks up on wake and starts late, and a machine that is off or asleep straight through simply misses the run; status then reports it as missed: was scheduled for <time>, and scheduling a fresh --at for that plan is allowed rather than blocked. Wiggum will not keep your machine awake for you — no caffeinate, no pmset, not for the wait and not for the run. Whether the lid-shut Mac stays up is your call about your own hardware, not a tool's to take from a shell. If you want the run to happen, keep the machine awake yourself.
One invocation, one run. --at never writes a crontab line, a LaunchAgent plist, or anything else that outlives the run. For a genuinely recurring schedule — nightly, weekly — use cron or launchd, which do it better; see Scheduling unattended runs and examples/wiggum-nightly-setup.sh.
Run several plans back to back, each in a fresh session, stopping at the first failure:
wiggum chain docs/schema_plan.md docs/api_plan.md docs/ui_plan.md
wiggum chain docs/*.plan.md --max-iterations 5
chain runs wiggum execute on each plan in order. If a plan fails (stalls or errors), the chain stops there rather than wasting effort on plans that likely depend on it. This is the preferred way to tackle work too large for one plan: split it into focused plans and chain them, instead of writing one 40-task plan that tends to stall.
Chains normally take their plan list from the command line, which fixes it at launch. --queue reads the list from a file instead, and re-reads it after every plan:
wiggum chain --queue docs/queue.txt --max-iterations 12
echo docs/extra_plan.md >> docs/queue.txt # while the chain is running
One path per line, # starts a comment, blank lines ignored. A plan appended while the chain works is picked up when the current plan finishes, a plan already run is never repeated even if you edit the file, and a queued path that does not exist when its turn comes stops the chain rather than being skipped silently. Because the list is on disk rather than in argv, a killed chain resumes by re-running the same command.
Each plan in a chain registers itself while it is the active one, so wiggum top shows a running chain as a single row for the plan it is on right now — and drops that row when the chain moves to the next plan. To supervise a chain, use wiggum watch --chain; wiggum watch <plan> attaches to one plan, not to the chain as a whole.
To run a chain unattended, background the chain rather than its plans: wiggum chain a.md b.md --background (or --at <when>) detaches the whole chain as one process, and the plans still run one at a time. The first plan's .pid and .out name the chain until it moves on; every plan writes its own .out as it runs, so wiggum status <plan> reads each plan's verdict afterwards and wiggum watch --chain <pid> follows the process. A backgrounded chain dies with the session that launched it, exactly as execute --background does (see Background runs & supervision).
A plan later in the chain has no pidfile until its turn comes. watch waits for it rather than reporting it missing — it prints what is running now and attaches when the plan starts, so watching plan three of six is one command and not a poll loop. --chain goes further and follows the process: it re-reads the registry entry, so it announces each plan as the chain reaches it and keeps streaming through the gaps between them.
Name the run by plan file or by pid; with neither, --chain takes the only live run, or — when several are live — the only one under the current directory, since standing in a project is already an answer. --here asks for that narrowing explicitly and refuses to reach outside it.
$ wiggum watch --chain
More than one run is live; taking the one in /Users/me/veritametrics.
Pass a pid or a plan file to follow another.
Following wiggum run 70613 across its plans...
=== now on: docs/site-improvements_plan.md ===
[2026-09-05 16:57:24] stop: all tasks complete after iteration 5
=== now on: docs/compare-campaigns_plan.md ===
[2026-09-05 17:11:01] phase: 2 - iteration 1 of 30 (12 tasks remaining, 0 dropped)
It streams whichever of .out and .log the run is writing, so a foreground chain — which writes no .out — is followed by its log. It returns when the process does, and non-zero only if the last plan recorded a status other than complete: a foreground chain writes no .out at all, so "no status recorded" is its ordinary ending, and inventing a failure there is the "any non-zero means gone" mistake that hand-rolled waiters make.
watch also prints the pid it read before testing whether it is alive, so a killed run that left its sidecar behind produces a Watching wiggum run ... (pid N) line and then returns at once. Read the return value, not the opening line.
Wiggum ships a /wiggum slash command for Claude Code that acts as an orchestrator for the CLI. Instead of re-running the loop in-conversation, the skill drives the wiggum binary: it creates a workplan, launches wiggum execute (in the background), monitors progress, waits for completion, analyzes whether a run is blocked, kills a run that overruns (only that run's process), and chains workplans together.
/wiggum docs/login-bug.md
/wiggum "add rate limiting to the /api/upload endpoint"
/wiggum chain: docs/schema_plan.md docs/api_plan.md docs/ui_plan.md
The skill accepts an issue file, a plain-text description (which it turns into a plan), an existing plan file, or several plans to chain. Because it drives the CLI, the wiggum binary must be installed and on PATH (see Background runs & supervision for the commands it uses).
The skill is installed globally by install.sh (to ~/.claude/skills/wiggum/SKILL.md) so it works in every project. Running wiggum init in a specific project installs a project-local copy at .claude/skills/wiggum/SKILL.md, which you can commit so teammates get it too.
wiggum init also writes .claude/rules/wiggum.md, a project rule covering what has to hold before anyone reaches for the skill:
Acceptance: per task- [ ] checkboxes, and [~] means dropped--max-iterations to the open checkboxes, and why a flaky suite isn't a reason to raise itchain --queue for appending work to a chain that's already runningtop, status, watch, and watch --chain — and how to read a stop reasonThe split matters. A rule with no paths: frontmatter loads at session start at the same priority as .claude/CLAUDE.md, so it costs context in every session in that project — which is why it's ~110 lines. The skill is an order of magnitude bigger and loads only when the work calls for it.
Both files are written without asking, because writing a file that isn't there takes nothing away. If a copy from an older wiggum is already present and its content differs, init says so and asks before overwriting; declining keeps yours, and --yes takes the update. wiggum init never touches your CLAUDE.md.
One command (macOS and Linux; on Windows use WSL):
curl -fsSL https://raw.githubusercontent.com/benman1/wiggum/main/get.sh | bash
It downloads the repo to a temporary folder, runs install.sh from it and cleans up. It asks for sudo only if /usr/local is not writable for you. Set WIGGUM_REF=<tag> to install a specific release, or WIGGUM_PREFIX=$HOME/.local to install without sudo.
Or clone the repo and run the install script yourself:
git clone https://github.com/benman1/wiggum.git && cd wiggum
./install.sh
This copies wiggum.sh and lib/wiggum.sh to /usr/local/lib/wiggum/, symlinks /usr/local/bin/wiggum to the entry point, seeds ~/.wiggumrc from the example config if you don't have one yet, and installs the /wiggum Claude Code skill globally. May prompt for sudo. Set WIGGUM_PREFIX to install somewhere other than /usr/local.
Upgrading while a run is in flight is safe. Each file is renamed into place rather than rewritten, so a run already under way keeps the version it started on and finishes on it; the next run picks up the new one.
Or just run it directly from the repo without installing:
./wiggum.sh plan issues/something.md
The CLI resolves the library relative to its own location, so no install step is needed for local use.
# Auto-detect project type from files in the current directory
wiggum init
# Specify a preset explicitly
wiggum init python
Available presets:
| Preset | Detected by | Verification steps |
|---|---|---|
node | package.json | type-check, test, build, lint --fix |
next | next.config.{js,ts,mjs} | type-check, test, build, lint --fix |
python | pyproject.toml, setup.py, or requirements.txt | ruff format+check (autofix), pytest |
astro | astro.config.{mjs,ts} | type-check, test, build, prettier (autofix) |
bash | .shellcheckrc or test/run.sh | shellcheck, bats |
If no preset is given, wiggum inspects the current directory and picks the best match. The generated .wiggumrc is a starting point -- edit it to match your actual scripts.
After creating the .wiggumrc, init writes the two Claude Code files described under Claude Code skill, and reminds you to create a CLAUDE.md if one doesn't exist.
On a fresh project it asks nothing. The only question init has is whether to overwrite something already there — a .wiggumrc, or a skill or rule left by an older wiggum whose content has since changed. Declining keeps your file untouched.
# From a single issue file
wiggum plan issues/login-bug.md
# From multiple spec files
wiggum plan docs/spec.md docs/requirements.md
# With a custom output path
wiggum plan issues/login-bug.md --plan-file docs/login_plan.md
# From a string (plan goes to stdout)
echo "Add dark mode toggle to settings" | wiggum plan
# Here-string shorthand
wiggum plan <<< "Fix the login timeout bug"
When given files, default output is <input-basename>_plan.md in the same directory as the first input file. When reading from stdin, the plan is written to stdout (use --plan-file to override).
# Execute with defaults (3 iterations)
wiggum execute docs/login_plan.md
# More iterations for larger plans
wiggum execute docs/plan.md --max-iterations 10
# Custom summary location
wiggum execute docs/plan.md --summary-file reports/run1_summary.md
# Execute and update README when done
wiggum execute docs/plan.md --update-docs README.md
# Update multiple docs after execution
wiggum execute docs/plan.md --update-docs README.md,docs/API.md
# Multiple context files (plan + supporting docs)
wiggum execute docs/plan.md docs/api_spec.md docs/schema.csv
# Pipe a plan directly from stdin (plan streams to execute, no intermediate file)
wiggum plan issues/bug.md | wiggum execute
# One-liner: describe, plan, and execute
echo "Add rate limiting to /api/upload" | wiggum plan | wiggum execute
# Run detached, then supervise it (status / wait / stop if it overruns)
wiggum execute docs/plan.md --background
wiggum status docs/plan.md
wiggum watch docs/plan.md --timeout 1800 --kill-on-timeout
Two equivalent patterns for plan → execute:
execute at the plan file on disk.
wiggum plan issues/bug.md # writes issues/bug_plan.md
wiggum execute issues/bug_plan.md
plan detects the non-TTY stdout and streams the plan into execute. No intermediate file.
wiggum plan issues/bug.md | wiggum execute
Default output: <input-basename>_summary.md in the same directory as the first input file.
Plan from multiple issues at once. All files are passed to Claude as context for a single combined plan:
wiggum plan issues/auth-bug.md issues/rate-limiting.md issues/logging-gap.md
# produces: issues/auth-bug_plan.md (named after the first file)
wiggum plan issues/auth-bug.md issues/rate-limiting.md --plan-file docs/sprint_plan.md
# better: explicit output name for combined plans
Run multiple plans sequentially. Use a simple loop to execute several plans one after another:
for plan in docs/*_plan.md; do
wiggum execute "$plan" --max-iterations 3
done
Plan all issues in a directory. Bash expands globs before wiggum sees them:
wiggum plan issues/*.md --plan-file docs/backlog_plan.md
Resume a partially-completed plan. If a previous run only finished some tasks, just run execute again. Phase 1 syncs the checkboxes against the repo, so Claude picks up where it left off:
# First run: completes 4 of 10 tasks
wiggum execute docs/plan.md --max-iterations 4
# Second run: starts from task 5
wiggum execute docs/plan.md --max-iterations 6
Pass supporting context alongside a plan. Extra files (specs, schemas, PDFs) are passed to Claude as context but aren't treated as the plan:
wiggum execute docs/plan.md docs/api_spec.md docs/schema.csv
wiggum <mode> [files...] [options]
command | wiggum <mode> [options]
Modes:
init Generate a .wiggumrc for the current project (--yes to not be asked)
plan Create a workplan from issue/spec files
explain Explain a plan's worth and its open decisions (read-only)
execute Implement a workplan with iterative validation
check Run verification waterfall and fix issues
docs Update documentation from input files
run Feed a series of prompts to Claude in one continuous session
status Show task progress and run state for a plan
watch Follow a run until it finishes (wait); --chain follows a chain
kill Stop a background run (only that run's process)
chain Execute several workplans back to back
top List every wiggum run on this machine at a glance
Options:
--plan-file <path> Output path for the plan (plan mode)
--summary-file <path> Output path for the summary (execute mode)
--explain-file <path> Write the explanation to a file instead of stdout (explain mode)
--no-feedback Skip the feedback pass over the finished plan (plan mode)
--queue <file> Read the plan list from a file, re-read after each plan (chain mode)
--json Emit runs as JSON instead of a table (top mode)
--max-iterations <n> Maximum implementation iterations (execute/chain, default: 30)
--benchmark <script> Run script after each iteration, feed output to Claude (repeatable)
--update-docs <files> Comma-separated doc files to update after execution (execute mode)
-b, --background Run execute or chain detached; supervise with status/watch/kill
--timeout <seconds> Stop watching after N seconds, 0 = forever (watch mode)
--kill-on-timeout On watch timeout, kill the run (watch mode)
--poll-interval <secs> How often watch polls for new output (default: 5)
--heartbeat <secs> Say the watch is alive after this much silence
(watch mode; default: 60, 0 = never)
--tail <lines> Backlog to show before following (watch mode;
default: 20, 0 = only new output)
--chain Follow the run across plans, not just this one (watch mode)
--here With --chain and no name, take the run in this directory
--no-verify Skip wiggum's verification waterfall (execute mode; rejected by check)
--no-commit Skip every wiggum-issued git commit (execute, check, docs)
-f, --prompts-file <p> Read prompts from a file, split on delimiter lines (run mode)
--session-file <path> Persist/resume the session id across invocations (run mode)
--new-session Ignore an existing --session-file and start fresh (run mode)
--delimiter <str> Prompt separator for -f/stdin (run mode, default: ---)
--effort <level> Reasoning effort: low|medium|high|xhigh|max (default: xhigh)
--permission-mode <m> Claude permission mode (default: bypassPermissions)
--model <name> Model for this call: alias (sonnet/opus/...) or full name
(default: model_plan/model_execute, or Claude Code's own default)
--verbose Show Claude output (suppressed by default)
-i <files...> Input files (docs mode)
-o <files...> Output doc files to update (docs mode)
-- End of options (remaining args are files)
-h, --help Show help
When no files are given, plan and execute read from stdin. Plan writes to
stdout whenever stdout is not a terminal (e.g. pipe or redirect), otherwise
to `<basename>_plan.md` on disk. Execute always writes to files (side
effects are the output). Use -- to pass filenames that start with a dash.
By default, Claude's response text is suppressed — wiggum shows only its own status lines. Pass --verbose to see Claude's full output at each step:
wiggum execute docs/plan.md --verbose
This is useful for debugging or understanding what Claude is doing at each step.
Wiggum looks for a .wiggumrc file, first in the current directory, then in $HOME. The config uses a simple key = value format.
| Key | Description | Default |
|---|---|---|
verify | A shell command to run as a verification step. Fails are sent to Claude for fixing. Multiple lines define an ordered waterfall. | (none) |
autofix | Like verify, but the command is run once first to let it self-correct (e.g. linters with --fix). Only escalates to Claude if it still fails after autofix. | (none) |
benchmark | A shell command to run after each iteration. Output is fed to Claude as context for the next iteration. Purely informational — does not gate or block. Multiple lines are supported. | (none) |
max_iterations | Maximum implementation iterations per run. Stops early if all tasks complete or progress stalls. | 30 |
max_validation_retries | Max times the validation cycle retries before giving up. | 5 |
skip_verify | If true, skip wiggum's verification waterfall entirely (same as --no-verify). | false |
skip_commit | If true, skip every wiggum-issued git commit (same as --no-commit). | false |
effort | Reasoning effort passed to Claude Code on every call: low, medium, high, xhigh, or max (same as --effort). | xhigh |
permission_mode | Claude Code permission mode for every wiggum-issued call: acceptEdits, auto, bypassPermissions, default, dontAsk, or plan (same as --permission-mode). | bypassPermissions |
model_plan | Model Claude Code uses for wiggum plan (alias like sonnet/opus, or a full model name). | (Claude Code's own default) |
Both behaviors are opt-in via CLI flags or config keys. CLI flags win when they disagree with config.
--no-verify / skip_verify = true — skip wiggum's verification waterfall in execute. This is useful when iterating on experimental code where running the full test suite each cycle is too slow, or when tests are known broken and not in scope for the current run. Caveat: this only suppresses wiggum's own verify pass. Claude may still run tests, builds, or type-checks during implementation — wiggum does not control Claude's tool use. To stop tests from running entirely, edit your project's CLAUDE.md to instruct Claude not to run them. wiggum check --no-verify is rejected (it would make check a no-op).
--no-commit / skip_commit = true — skip every wiggum-issued git commit. Useful when reviewing changes manually before committing, or when exploring multiple approaches in an unstaged working tree. The working tree is left dirty; subsequent wiggum execute runs will see uncommitted state in phase 1, and Claude may try to reconcile it. Note: Claude may still create its own commits if it decides to — this flag only stops the wiggum-issued prompts.
wiggum execute docs/plan.md --no-verify # skip the verify waterfall
wiggum execute docs/plan.md --no-commit # leave changes uncommitted
wiggum execute docs/plan.md --no-verify --no-commit # both
wiggum check --no-commit # verify but don't commit fixes
Two settings control how every wiggum-issued claude call behaves. Both can be set in .wiggumrc or per-run on the CLI; the CLI wins on conflict, and both apply to all modes (plan, execute, check, docs, run).
effort / --effort — the reasoning effort Claude Code uses: low, medium, high, xhigh, or max. Wiggum defaults to xhigh (deep reasoning suits planning and self-healing). Drop to low/medium for cheaper, faster runs on simple tasks.
permission_mode / --permission-mode — the Claude Code permission mode for the call: acceptEdits, auto, bypassPermissions, default, dontAsk, or plan. wiggum init asks for this and writes permission_mode = auto by default — Claude's auto-mode classifier gates each action, a guardrailed middle ground for unattended runs. bypassPermissions runs everything with no checks (fastest, no guardrails). When the line is absent from .wiggumrc entirely, the built-in default is bypassPermissions (back-compat for configs written before init set this).
wiggum execute docs/plan.md --effort max # maximum reasoning
wiggum run --permission-mode auto "tidy up the imports" # let auto-mode decide
# .wiggumrc
effort = xhigh
permission_mode = bypassPermissions
Unlike effort and permission mode, the model is set per phase: model_plan for wiggum plan, model_execute for wiggum execute (and chain, which calls execute once per plan). Each defaults to Claude Code's own default model when unset, so an existing .wiggumrc behaves exactly as before. --model overrides whichever phase the current command runs, the same way --effort/--permission-mode override for a single invocation.
Sonnet is enough for most planning and implementation work; reach for Opus only on plans or codebases that need deeper reasoning. Since plan and execute are separate commands, you can run cheaper on one and not the other:
wiggum plan issues/login-bug.md --model sonnet # a quick plan
wiggum execute docs/login-bug_plan.md --model opus # deeper reasoning for the implementation
# .wiggumrc
model_plan = sonnet
model_execute = sonnet
Accepts a model alias (sonnet, opus, fable) or a full model name, exactly as claude --model does.
Both verify and autofix define shell commands that run after each implementation step. The difference is what happens on failure.
verify -- strict check. If the command exits non-zero, wiggum captures the last 60 lines of output and sends them to Claude with a prompt to fix the code. Use this for tools that only report problems without fixing them: type checkers, test suites, builds.
autofix -- self-correcting check. Wiggum runs the command once and lets it modify files (e.g. ruff --fix rewrites source, prettier --write reformats). Then it runs the same command a second time to verify. Only if the second run still fails does Claude get involved. Use this for linters and formatters that have a --fix or --write mode.
You can mix both freely. They run in the order they appear in the config file.
The waterfall short-circuits: if the first step fails, later steps don't run until it's fixed. This prevents wasting time on a full build when there are type errors, and prevents running the test suite when the code doesn't even compile.
A well-ordered waterfall puts the cheapest and fastest checks first:
verify = npm run type-check # Seconds. Catches most issues.
verify = npm test # Tens of seconds. Catches logic errors.
verify = npm run build # Minutes. Catches integration issues.
verify = ./smoke.sh # Validates the built output end-to-end.
autofix = npx prettier --write . # Auto-corrects formatting.
When any step fails, wiggum asks Claude to fix it (or runs autofix), then restarts the entire waterfall from the top. This continues up to max_validation_retries times. If validation still hasn't passed after all retries, wiggum logs a warning and moves on to the next iteration.
A smoke test is a lightweight end-to-end check that validates the built output actually works. Unlike unit tests (which test individual functions) or build commands (which test compilation), a smoke test exercises the real artifact: start the server, hit a URL, check the response.
Smoke tests are project-specific, so wiggum doesn't generate them -- you write a small script and point to it in your config:
verify = ./smoke.sh
Some examples of what a smoke test might do:
/health, verify the response, kill the server.--help, verify it exits 0 and prints usage.A minimal smoke test for an Astro or Next.js static export:
#!/usr/bin/env bash
# smoke.sh - Verify the build output is navigable
set -euo pipefail
npm run build
npx serve dist -l 4321 &
SERVER_PID=$!
sleep 2
# Check that key pages return 200
for path in "/" "/about" "/api/health"; do
status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:4321${path}")
if [[ "$status" != "200" ]]; then
echo "FAIL: $path returned $status"
kill $SERVER_PID
exit 1
fi
done
kill $SERVER_PID
echo "Smoke test passed."
Because smoke tests are just verify lines, they participate in the normal waterfall: if the smoke test fails, Claude sees the output and tries to fix the underlying issue.
Benchmarks are different from verification steps. Verification is pass/fail — "is the code correct?" Benchmarks answer "is the code better?" Their output is fed to Claude as context for the next iteration, but they never block or gate progress.
Configure them in .wiggumrc, on the command line, or both:
# .wiggumrc
benchmark = ./scripts/measure_bundle_size.sh
benchmark = ./scripts/lighthouse_score.sh
wiggum execute docs/plan.md --benchmark "./measure_load_time.sh"
After each implementation iteration (post-commit), wiggum runs all benchmark scripts and includes their concatenated output in the next iteration's prompt. Claude uses this to guide its approach — focusing on what's improving and what isn't.
Examples of benchmark scripts:
du -sh dist/ && wc -l dist/**/*.jsnpx lighthouse http://localhost:3000 --output json --quiet | jq '.categories.performance.score'npx jest --coverage --coverageReporters=text-summary 2>&1 | grep StatementsIf no benchmark scripts are configured, the loop works exactly as before.
TypeScript / Node.js project:
# .wiggumrc
verify = npm run type-check
verify = npm test
verify = npm run build
autofix = npm run lint -- --fix
max_iterations = 5
Python project:
# .wiggumrc
autofix = ruff format app tests && ruff check --fix app tests
verify = pytest
max_iterations = 30
max_validation_retries = 3
Astro / static site with smoke test and performance benchmark:
# .wiggumrc
verify = npm run type-check
verify = npm test
verify = npm run build
verify = ./smoke.sh
autofix = npm run format
benchmark = du -sh dist/
Next.js project:
# .wiggumrc
verify = npm run type-check
verify = npm test
verify = npm run build
autofix = npm run lint -- --fix
max_iterations = 30
Minimal (no verification):
# .wiggumrc
max_iterations = 2
Without any verify or autofix lines, wiggum still implements and commits but skips the validation loop entirely.
./.wiggumrc (project root -- per-project settings)~/.wiggumrc (home directory -- personal defaults)Only the first file found is used. They are not merged.
Every Claude call wiggum makes runs under one permission mode, passed as --permission-mode on the command line. There is no per-phase variation: planning, implementation, validation fixes and commits all use the same mode.
Where the mode comes from, highest precedence first:
| Source | Value |
|---|---|
--permission-mode <mode> on the wiggum command | that mode, for this run only |
permission_mode = <mode> in .wiggumrc | that mode, for this project |
| Neither set | bypassPermissions (the original default, kept for back-compat) |
wiggum init writes permission_mode = auto, so a project set up with init runs guardrailed rather than falling through to that back-compat default.
| Mode | What it does |
|---|---|
auto | Claude's auto-mode classifier decides each action. Unattended but not unchecked — the right setting for a wiggum run. |
bypassPermissions | Every action runs with no checks. Fastest, no guardrail. |
acceptEdits | Auto-approves file edits but still prompts for shell commands, so an unattended run blocks waiting for input. Only useful when you're watching. |
default, dontAsk, plan | Passed through to Claude Code unchanged. |
Because wiggum passes the mode explicitly on every call, a .claude/settings.local.json allow list is not what gates a wiggum run: under bypassPermissions it's ignored outright, and under auto the classifier decides. Wiggum doesn't write one for you.
An allow list is still worth having for your own interactive Claude Code sessions in the repo. That file is per-machine and not committed, so it's yours to shape:
{
"permissions": {
"allow": [
"Bash(git add *)",
"Bash(git commit *)",
"Bash(npm run *)",
"Bash(npx *)"
]
}
}
See the Claude Code permissions docs for the full rule syntax.
wiggum run is built for unattended use: feed it prompts, point --session-file at a stable path, and a scheduled job can pick up the same Claude session each time. The catch is the environment — cron runs your job with a minimal, non-login shell, so the three things below trip up almost every first attempt.
Initialize each project first. Run wiggum init in every repository you intend to schedule, before you schedule it — interactively, or with --yes from a setup script. A project without verification steps is the worst thing to run unattended: wiggum reads ./.wiggumrc if it exists and ~/.wiggumrc otherwise — never both — so if neither defines verify or autofix commands, a scheduled run will plan, implement and commit without ever running your tests, type checker or linter. init also sets up the Claude Code permissions an unattended run needs, and installs the /wiggum skill.
PATH is bare. Cron's PATH is typically just /usr/bin:/bin. Neither wiggum (/usr/local/bin) nor claude (~/.local/bin) is on it, and node (used by Claude Code plugin hooks) usually isn't either. Set PATH explicitly in the job.claude login is stored in the macOS Keychain, and a cron job has no security session: its default keychain is the System keychain, your login keychain reports "User interaction is not allowed", and reading the credential fails even when the item is named explicitly. claude auth status from cron returns loggedIn: false. Unlocking it non-interactively would mean putting your macOS account password in a file, which is a far worse trade than any token — don't. Either schedule via launchd (which runs in your login session, where it just works), or supply a credential through the environment: ANTHROPIC_API_KEY (API billing) or a long-lived token from claude setup-token (billed to your Claude subscription, not API rates). On Linux none of this applies — the credential is a plain file at ~/.claude/.credentials.json, which your cron job reads directly./usr/sbin/cron under System Settings → Privacy & Security → Full Disk Access.Cron one-liners and prompt quoting fight each other. Put the job in a small script so the environment lives in one place. A ready-to-edit wrapper ships with wiggum at examples/wiggum-cron.sh — copy it out and edit the three marked lines (PATH, auth, and your project path + prompt):
cp examples/wiggum-cron.sh ~/bin/wiggum-cron.sh
chmod +x ~/bin/wiggum-cron.sh
$EDITOR ~/bin/wiggum-cron.sh # set PATH, auth, project dir, and the prompt
# Test in a clean environment FIRST — this is where PATH/auth bugs surface:
env -i HOME="$HOME" ~/bin/wiggum-cron.sh
The wrapper's core is just the environment fixes plus the call:
export PATH="/usr/local/bin:$HOME/.local/bin:/opt/homebrew/bin:/usr/bin:/bin"
export ANTHROPIC_API_KEY="sk-ant-..." # or a `claude setup-token` token
cd "$HOME/path/to/your/project"
wiggum run --session-file "$HOME/.wiggum-cron-session" \
--effort high --permission-mode auto \
"Summarize commits since the last run and append a bullet to STANDUP.md"
Then add it to your crontab (crontab -e). This runs at 9:00 AM daily and logs both Claude's responses (stdout) and wiggum's status (stderr):
0 9 * * * /Users/you/bin/wiggum-cron.sh >> /Users/you/wiggum-cron.log 2>&1
wiggum run suits a single recurring prompt. For the fuller loop — plan a sweep,
then execute the plan it produced — two examples ship ready to use.
Run the setup once per project:
./examples/wiggum-nightly-setup.sh
It asks for the directory, start time, days and iteration limit, copies the
runner to ~/bin/wiggum-nightly.sh, and installs the schedule using whatever the
platform provides:
| scheduler it installs | how the run is signed in | |
|---|---|---|
| Linux | a crontab entry | reads ~/.claude/.credentials.json directly |
| macOS | a LaunchAgent | runs inside your desktop session, using the browser login |
| Windows | not implemented | — |
Run it again for the next project: each gets its own schedule, and re-running it for a project you have already scheduled replaces just that entry.
Why macOS gets a LaunchAgent instead of a cron job. Not preference — measurement. The same script, run as the same user in the same minute:
| cron | LaunchAgent | |
|---|---|---|
| session manager | Background | Aqua |
| default keychain | /Library/Keychains/System.keychain | your login keychain |
| read the credential | fails | succeeds |
claude auth status | loggedIn: false | loggedIn: true |
A cron job runs as you, but outside your desktop login session, so the login keychain is not unlocked for it. The only way to unlock it non-interactively is to put your macOS account password in a file — much worse than any token, so the setup does not offer it. A LaunchAgent runs in that session, so your existing browser login just works with nothing to paste. It does need you logged in to the desktop: asleep and screen-locked are both fine, fully logged out is not.
If you would rather keep cron on macOS, that works too — but it needs a
credential in the environment (claude setup-token, billed to your subscription).
This setup script does not do that; write the crontab line yourself.
First run, in order:
wiggum init in each project you intend to schedule — or wiggum init --yes from a setup script, which takes every overwrite without asking../examples/wiggum-nightly-setup.sh, once per project.~/bin/wiggum-nightly.sh /path/to/project 3.There is no credential step, on either platform, and nothing to grant in System Settings.
Missed runs stay missed. launchd starts a job it missed as soon as the
machine wakes, which is rarely what you want from an overnight sweep. The runner
therefore checks the clock against WIGGUM_NIGHTLY_AT (set by the schedule) and
stands down if the slot has passed — so a 01:00 run does not begin at 09:00 when
you open the lid. Widen the tolerance with WIGGUM_NIGHTLY_WINDOW (minutes,
default 10), or unset WIGGUM_NIGHTLY_AT to always run.
The runner itself is examples/wiggum-nightly.sh,
a plain script taking <project-directory> [max-iterations]. It writes a
planning brief into docs/, runs wiggum plan on it, and executes the result.
Nothing in it is platform-specific, so the same file is what both schedulers
call. Edit the copy in ~/bin/ to change the brief — the setup script never
overwrites an installed runner, so your edits survive.
One detail worth knowing if you write your own: pass --plan-file. When
stdout is a redirected log rather than a terminal, wiggum plan treats the run
as piped — it prints the plan to stdout and deletes the file. Naming the plan
file explicitly keeps it on disk for execute to pick up.
--session-file on every run so each job builds on the last. Reset occasionally with --new-session so the conversation doesn't grow without bound.--session-file (or always pass --new-session) so each run starts fresh.On macOS, launchd is more reliable than cron for user agents — it survives reboots, handles logging, and doesn't need the Full Disk Access workaround. Create ~/Library/LaunchAgents/com.you.wiggum.plist pointing ProgramArguments at the same wrapper script, set StartCalendarInterval, then launchctl load it. The wrapper script and its environment notes above apply unchanged.
| Mode | Default output | Override flag |
|---|---|---|
plan (TTY stdout) | <dir>/<basename>_plan.md | --plan-file |
plan (piped stdout or stdin input) | stdout | --plan-file |
execute | <dir>/<basename>_summary.md | --summary-file |
execute (stdin) | docs/stdin_summary.md | --summary-file |
execute/plan/docs | <dir>/<basename>.log | (always created) |
execute | <dir>/<basename>.pid | (always created; removed when the run ends) |
execute --background | <dir>/<basename>.pid, <dir>/<basename>.out | (always created) |
chain --background | the first plan's .pid and .out, then each plan's own .out as its turn comes | (always created) |
execute (any mode) | ~/.wiggum/runs/<pid> | WIGGUM_REGISTRY_DIR |
The last one is the machine-wide run registry — one small file per run in flight, named by pid and holding the absolute path of the plan it is working on. It is what lets wiggum top answer "what is running" rather than "what is running here", and it is the only file wiggum writes outside the project directory. Entries are pruned whenever top reads them and finds a dead pid — or a pid that has been reused — so a killed run or a reboot leaves nothing stale behind. Point WIGGUM_REGISTRY_DIR somewhere else to keep runs out of the shared view (a test harness should).
A pid alone is not an identity, so the .pid records two lines: the pid, then the time that process started. The kernel reuses pids. A sidecar left behind by a crash, a kill -9 or a reboot will eventually name a process that belongs to somebody else, and every question wiggum asks of it then gets a confident wrong answer — top invents a running run, status agrees, and kill signals a stranger's process tree, children included. Wiggum compares the recorded start time (what ps -o lstart= prints, never parsed, only compared) before believing a pid or signalling it: a reused pid reads as "not running", and kill says so and clears the sidecar instead of firing. The machine-wide registry records the same thing. A sidecar written by an older wiggum carries no start time and falls back to bare liveness, which is what wiggum did before — kill warns that it cannot verify such a pid rather than silently trusting it. One check needs no identity at all and so reaches those older sidecars too: a run that recorded a final status is over, whatever its pid now answers. A .pid sitting beside an .out that says Status: complete names a run that ended; if that pid is alive it belongs to something else, and kill clears the sidecar and signals nothing. The status is read for the current run only, so relaunching over the same sidecars is never mistaken for the last run's finish.
The directory and basename are derived from the first input file. When reading from stdin, files default to docs/. The sidecars sit next to the plan and are how status/watch/kill/top find a run. Every run writes a .pid while it is working; a foreground run removes its own when it ends, while a background run's is left behind (its .out holds the final status) until watch/kill clear it. For example:
wiggum plan docs/auth-issue.md
# produces: docs/auth-issue_plan.md
wiggum execute docs/auth-issue_plan.md
# produces: docs/auth-issue_plan_summary.md
echo "Fix auth bug" | wiggum plan
# produces: plan on stdout
echo "Fix auth bug" | wiggum plan --plan-file docs/plan.md
# produces: docs/plan.md
wiggum/
wiggum.sh CLI entry point (thin wrapper)
lib/wiggum.sh Core library (all logic, sourceable by tests)
install.sh macOS installer
completions/ Bash and zsh shell completions
examples/
wiggum-cron.sh Wrapper template for running `wiggum run` from cron
wiggum-nightly.sh Plan a sweep in one project, then execute it
wiggum-nightly-setup.sh Schedule wiggum-nightly.sh per project (cron)
test/
wiggum.bats Bats test suite
run.sh Test runner (shellcheck lint + bats)
site/
index.html Landing page, published to GitHub Pages
docs/ Workplans, summaries and run sidecars (not the website)
.github/workflows/
pages.yml Deploys site/ on any push that touches it
LICENSE MIT
.wiggumrc Self-hosting config (wiggum tests itself)
.wiggumrc.example Example config with comments
CLAUDE.md Project standards for Claude Code
.claude/
settings.local.json Per-machine Claude Code permissions (not committed)
rules/wiggum.md Always-loaded project rule, written by `wiggum init`
skills/wiggum/
SKILL.md /wiggum slash command for Claude Code
Run the test suite (lint + unit tests):
./test/run.sh
This runs shellcheck on all shell scripts, then the Bats test suite. Requires bats-core and shellcheck:
brew install bats-core shellcheck
The landing page is a single self-contained file, site/index.html — no build step, no
dependencies. Open it directly to preview it:
open site/index.html
Pushing any change under site/ deploys it to https://benman1.github.io/wiggum/ via
.github/workflows/pages.yml. It is served from site/ rather than docs/ because
docs/ is where wiggum keeps its own plans, summaries and run sidecars — publishing
that directory would put every workplan in the repo on the web.
MIT — see LICENSE.
--max-iterations 1 for a trial run to see how wiggum interprets your plan before committing to a longer run.wiggum plan is cheap. Edit the generated plan to remove, reorder, or clarify tasks before feeding it to execute.max_validation_retries. If you're paying close attention to token costs, decrease it.--verbose to debug. Claude's output is suppressed by default. Pass --verbose to see what Claude is doing at each step.claude -r. Wiggum logs a Claude session ID for every step. Find the session ID in the .log file and resume it interactively: claude -r <session-id>. Useful for asking follow-up questions about what Claude did during a specific implementation or validation step.CLAUDE.md from the project root. Put your architecture, conventions, and coding standards there so Claude writes code that fits your project. wiggum init reminds you if one is missing, and writes its own guidance to .claude/rules/wiggum.md rather than editing the file./wiggum inside Claude Code. If you're already in a Claude Code session and want to kick off the full loop without switching to the terminal, use /wiggum <issue>. It runs the same workflow natively.--background daemonizes inside the shell session that launched it. That is enough for a run you sit with, but a run expected to last hours dies when that session is destroyed: a closed terminal, an ended agent session, a supervising process exiting. The signature is a clean .out that stops mid-phase with no error and no summary file, which looks like a wiggum bug and is not one. nohup … & delays this rather than fixing it.
Launch anything long inside a detached multiplexer, with wiggum in the foreground inside it:
screen -dmS wig1 bash -lc 'conda activate myenv; \
exec wiggum execute docs/plan.md --max-iterations 12 >> docs/plan.out 2>&1'
No --background here: the multiplexer provides the durability, and a daemonizing child would let its session exit immediately and take the run with it. A foreground run still writes a .pid while it works, so wiggum status and wiggum top can find it — it just clears the sidecar when it ends rather than leaving a finished: row behind. For liveness across a whole screen session (which outlives any one plan) use screen -ls, or capture the PID and check it with kill -0 <pid> — the same primitive wiggum's own process_alive() uses. Capture ps -o lstart= -p <pid> alongside it and compare before you act on that pid: kill -0 alone cannot tell your run from whatever inherited its number, which is the check run_pid_alive() adds.
Prefer that PID check over matching command lines. pgrep -f errors (rather than returning empty) if any unrelated process on the machine has non-UTF-8 bytes in its command line, and ps -eo pid,command truncates to the terminal width, so in a background context the string you are matching can be cut off. Both failures look identical to "the job finished", so a until ! <check> waiter fires early. Confirm a completion by the artifact it was supposed to produce, never by the absence of a process alone.
Tasks that take longer than an iteration need care. An iteration completes roughly one task, so a task that trains a model or runs a long migration will not tick its checkbox inside that iteration. Wiggum records no progress, and two of those in a row stop the run while the work is still going. Write such tasks to launch their job into a separate detached session and to accept on the artifact's existence, so a later iteration sees the finished output instead of restarting an hour of work. Before treating No progress detected as a stall, check whether that job is still alive and its output still growing.
Two runs on one machine compete. wiggum runs Claude, the verify steps, and anything a task spawns. Two concurrent runs plus a training job on a 4-core machine reached a load average of 59, which tripled the time of every verify pass. Running two plans one after the other finishes both sooner than interleaving them.
Run wiggum top before launching another one. It lists runs from every project, not just the one you are standing in, which is the case that catches people out: an idle-looking directory is not an idle machine. Its footer answers the rest of the question — load against cores and swap used — and its RSS/CPU columns say which of several runs is the expensive one.
Watch the verification cost. verify and autofix run over the whole repo after every task, and again after each fix attempt (max_validation_retries, default 5). On a large suite that can dominate the run: check the phase2-validate-* timings in the .log before assuming the work itself is slow. --no-verify turns the sweep off for one run without touching your .wiggumrc, at the cost of the safety net, which suits a run whose own tests you are checking directly rather than one left unattended.
The .out file is append-only. It accumulates every run's history, so a grep over the whole file will match a stall from a previous run and report it as current. Baseline your match count before launching, or scan only after the last === WIGGUM EXECUTE MODE === banner.
model_execute |
Model Claude Code uses for wiggum execute (and chain, which calls it per plan). |
| (Claude Code's own default) |