CLI reference
text
bunker - run coding agents in sandboxed git worktrees
Usage:
bunker init [--yes | --explain <step>] guided first-run setup in 8 steps (--yes: accept defaults; --explain: re-read a step)
bunker doctor read-only setup check: ✓/✗ with a fix for each; exit 1 if anything required is missing
bunker "<prompt>" [options] single prompt: one agent run, merged back
bunker task "<p>" "<p>" ... parallel: one worktree per task
bunker review "<prompt>" implement -> review -> fix rounds
bunker issue <n> GitHub issue -> reviewed PR
bunker queue work the `bunker:queued` label queue
bunker watch keep working the queue unattended (implies --merge)
bunker shepherd one pass over the open bunker PRs: fix red CI, update behind branches, resolve conflicts
bunker status [--json] read-only watch summary: watcher, current run, queue, merged/failed with reasons, spend
bunker ui [--port <n>] shared live view: one tab per repo (default port 4321)
bunker audit file improvement ideas as `discovered` issues
bunker triage [--yes] read-only agent verifies open `discovered` issues (dry run unless --yes)
bunker clean [--yes] [--logs <days>] [--images] [--force] remove leftover worktrees and branches (dry run unless --yes)
bunker self-update [--channel main] build and install the latest v* release tag (--channel main: pull main instead)
Common options (all modes):
--model <id> model for every role (implementer, reviewer, auditor)
--implementer-model <id> model for the implementer
--auditor-model <id> model for the audit agent
--image <name> use this docker image as-is
--no-sandbox run the agent on the host (empty HOME, no isolation)
-h, --help show this help (wins over every other flag)
--verbose stream all agent, setup and verify output (also BUNKER_VERBOSE=1)
--version print the version and the tag (or main@<sha>) it was built from
--no-pull (issue, queue, watch, audit) skip the `git pull --ff-only` of your checkout that
otherwise runs first, only when it has no tracked changes and is on the default branch
Single prompt and task:
--max-iterations <n> re-run the agent up to n times in one worktree (default 1)
bunker task "<prompt>" "<prompt>" ... each prompt is a task with its own worktree
review, issue, queue, watch:
--max-rounds <n> review rounds before giving up (default 2)
--reviewer-model <id> model for the reviewer
--no-resume start every implementer round (and --max-iterations pass) in a fresh agent session instead of
resuming the previous one with the reviewer feedback
issue and queue:
issue <n> implement GitHub issue n and open a PR
queue run open issues labelled `bunker:queued`, in order: `priority:high`, `priority:medium`, no priority,
`priority:low`; within a priority `bug` issues first; then oldest first (watch re-orders between
issues). Prints the order with the reason per issue. Skips any with an open
`Blocked by #N` / `Depends on #N`. An agent that reports a blocked_by item in its <report>/<verdict> JSON
gets its blocker filed as `discovered` and the issue relabelled `bunker-blocked`
(no PR, doesn't count toward --limit); it returns to `bunker:queued` once the blockers close
--allow-labeler <login> (issue, queue, watch; repeatable) also accept the `bunker:queued` label from this GitHub user. By
default only repo admins and maintainers may queue work: otherwise the issue is skipped, the label
removed and a comment left. An issue never labeled "bunker:queued" is refused too (no label
event to check): add the label first. Also: allow_labelers = ["login"] in ~/.config/bunker/config.toml.
If the permission lookup fails the issue is skipped (fail closed)
--limit <n> (queue) max issues per run (default 5; blocked issues don't count)
--parallel <n> (queue --merge, watch, manual-merge mode) work up to n issues at once, one worktree + container each (default 1).
Merges stay serial, in queue order: each PR's branch is first updated to the latest default
branch and re-verified; a conflict or failure then gets one fresh attempt from the new main.
Trade-off: more concurrent spend, and rework when issues touch the same files; 2 is a good choice
--setup "<cmd>" run in the worktree before the agent (default: setup in config.toml)
--verify "<cmd>" run after approval; a failure stops before pushing (default: verify in config.toml)
--verify-on-host run --verify on the host instead of in the sandbox
--merge auto-merge the PR once checks pass (requires --verify or verify in config.toml)
--ci-timeout <min> (--merge) minutes to wait for the merge (default 30); under watch the shepherd waits, not the run
--idle-timeout <min> kill an agent or reviewer that prints nothing for this long; the step fails as
idle-timeout, worktree and branch kept (default 10)
--completion-timeout <s> seconds an agent may linger after its final result event or closing signal before it is
killed and the step counts as done, with a warning (default 60)
--step-timeout <min> overall limit for setup and verify, which can be quiet for long (default 30)
issue, queue, watch, audit, live view:
Every run publishes to a bunker hub (bunker ui) if one is reachable on 127.0.0.1:4321,
tagged with the repo (owner/repo from origin); with no hub it just carries on.
--ui make sure a hub is up (start one for this run if not), publish, print the URL
--port <n> (--ui) hub port to use (default 4321)
--ui-keep (--ui) keep the hub this run started alive after it ends (Ctrl-C to quit)
watch:
watch loop: work the queue, sleep, repeat until Ctrl-C; needs --verify or verify in config.toml
--interval <min> minutes to sleep when no work is waiting (default 10)
--for <duration> stop starting new issues after this long, e.g. 5h, 90m, 2h30m (default: until Ctrl-C)
--no-notify no desktop notifications (notify-send / osascript) for: paused on the usage limit, spend cap
reached, an issue failed or blocked, a pass that merged, watch stopped or crashed. Failed /
blocked comments @-mention notify.github_user (default: the repo owner, if a user account)
--max-cost-day <usd> stop starting issues once this much (notional USD, as `claude -p` reports it) was spent in a rolling 24 h,
then sleep until the window frees up; kept in .bunker/watch-spend.json (default: no cap).
Also max_cost_day in ~/.config/bunker/config.toml
--max-cost-month <usd> same for a rolling 30 days (default: no cap; config.toml: max_cost_month)
--audit-every <days> also run audit at most once per this many days, kept in .bunker/watch-state.json (default off; 30 is a good value)
--triage-every <hours> also run triage at most once per this many hours, comment and close only (default off)
--no-merge manual-merge mode (also `merge = "manual"` in config.toml): a run ends at an open PR labelled
`bunker:awaiting-review` and a notification; auto-merge is never requested and bunker never merges.
Issues with an open bunker PR are not started again
Under watch a run ends once its PR is open and auto-merge is armed, which frees the --parallel slot. The shepherd (the same
stage as `bunker shepherd`) then owns every open bunker PR (same-repo branch {feature|fix}/<n>-*, body "Closes #<n>") on each pass.
It keeps no state: it reads GitHub, so it carries on after a restart. A red required check goes, with its failed log, to a fix agent on the
PR branch (3 attempts, then `bunker:failed` on the PR and a notification; reading logs needs Actions: read on BUNKER_GH_TOKEN, else it
notifies you); a branch behind its base is updated; a conflict is resolved by an agent, verified, and reviewed again if the diff changed;
"changes requested" by a maintainer goes to the agent and the review is re-requested. It never merges: auto-merge plus a required check is the gate
Failed issues get `bunker:failed` instead of `bunker:queued` and are never retried automatically; a running one shows `bunker:in-progress`.
audit:
audit read-only agent run that files issues
--max-issues <n> max issues to file (default 5); only high/medium severity are filed, as `discovered` + `severity:*`
--focus "<text>" steer the audit toward a topic
--setup "<cmd>" run in the worktree before the agent
triage:
triage read-only agent (same sandbox as audit) checks each open `discovered` issue without a triage comment
against the default branch: fixed, as-designed, duplicate or valid (with a suggested priority and bug).
Bunker posts the verdict as a comment, sanitised and redacted. Dry run by default: prints verdicts and
planned actions, changes nothing. Failed or unparseable verdicts leave the issue untouched
--yes (triage) comment and close fixed / duplicate issues (as-designed: closed not planned), and label valid
ones bunker:queued + priority:* + bug as you. Valid issues are never labelled bunker:queued without --yes
--limit <n> (triage) max issues per run, oldest first (default 10)
watch --triage-every runs triage unattended in comment-and-close mode only: it never labels
init and doctor:
bunker doctor read-only, safe to re-run: checks tools (docker, git, gh, claude), auth (Claude token, scoped
BUNKER_GH_TOKEN and the permissions it lacks), repo files, GitHub labels / auto-merge / CI /
required check, the model per role, and the effective value and source of every .bunker/config.toml
key (config.toml is validated). Prints ✓/✗ with a one-line fix; exit 1 if anything
required is missing
bunker init interactive, per repo, in 8 steps with a progress line: [1/8] Welcome, [2/8] Claude token (via
`claude setup-token`, validated), [3/8] GitHub identity (the exact fine-grained token checklist and a
pre-filled link; hidden input, validated live, stored 0600 in ~/.config/bunker/.env; a GitHub App or bot
account is offered as advanced), [4/8] Toolchain (npm/pnpm/yarn/bun, composer, python, cargo, go,
monorepos; asks when unsure; setup and verify are editable), [5/8] Sandbox image (builds it and runs
setup and verify once as an unattended run will, isolated; offers to edit and retry on failure),
[6/8] Merge safety (offers a CI workflow with a `test` job as a PR, a ruleset requiring it, and
auto-merge, through your own gh login; or prints the exact clicks), [7/8] Spend and models (daily cap,
default $20, in ~/.config/bunker/config.toml), [8/8] Labels, then an optional guided first task: a
tiny issue run attended without --merge. Nothing is written before a confirm that names the file or
setting; every question shows its default. Re-running skips what is already ✓ and says so; Ctrl-C
leaves no half-written files. Ends with doctor, a summary of what was written and what is still ✗,
and the first command to run. NO_COLOR is honoured; links are clickable where the terminal allows
bunker init --explain <step> re-read one step's guidance (a number 1-8 or a name: welcome, claude, github, toolchain,
sandbox, merge, spend, labels)
A one-word prompt "init" or "doctor" is read as the subcommand; to use it as a prompt put -- first: bunker -- init
(or make it longer than one word).
--yes (init) accept every default without asking; never overwrites a file, opens no PR and changes no
repo setting; the steps that need you (secrets, CI, ruleset) are listed at the end
status:
bunker status read-only, run in the repo. Prints whether `bunker watch` is alive (from .bunker/watch.lock: pid, version,
uptime), the run in flight and its stage, the queue in priority order, what merged and failed since the
watcher started (each failure with a one-line reason from its run log, the PR or log, and the `gh issue
edit` command that retries it), and spend in the last 24 h and 30 days against the caps. With no watcher
running it shows the last run's results. --json prints the same as JSON
clean:
bunker clean run from the repo's main checkout. Dry run by default: prints what it would remove and why.
Worktrees: .worktrees/<n>-* whose issue is closed or whose PR is merged/closed; /tmp/bunker-*
worktrees of this repo that no running process uses. Branches: bunker/<id> without a worktree;
{feature|fix}/<n>-<slug> whose PR is merged/closed or that have no commits beyond origin/<default>.
Anything with commits not on origin/<default>, or uncommitted changes, is first kept as the tag
bunker/stale/<name>-<timestamp> (printed). Only bunker's own names are touched. Ends with git worktree prune
--yes (clean) really remove
--logs <days> (clean) also remove .bunker/runs/*.log older than this (default: clean.logs_days from config, else keep all)
--images (clean) also remove bunker-repo-* docker images no running container uses (bunker-claude is kept)
--force (clean) go ahead although a watch lock for this repo is held by a live process (otherwise exit 2)
issue, queue and watch run the same worktree and branch cleanup at start-up, and Ctrl-C/SIGTERM removes the
current run's /tmp/bunker-* worktree and bunker/<id> branch if it has no commits.
Repo config (.bunker/config.toml, parsed as TOML; .bunker/Dockerfile stays a separate file):
Precedence: CLI flag > env var > config.toml > .bunker/copy > built-in default.
A leftover .bunker/setup or .bunker/verify file is an error: move it into config.toml. Unknown keys warn; a key
of the wrong type exits 2.
Keys (tables: [models], [skills], [watch], [merge], [clean], [notify]):
setup shell command run in each worktree before the agent
verify command that must pass before anything is pushed
verify_network let sandboxed verify use the network and Claude credentials (off: verify runs agent-written code) (default false)
verify_retries fix rounds the implementer gets when verify fails, before the run fails as verify-failed (0: none) (default 1)
check command (e.g. a typecheck) run in the isolated sandbox before each review; its output goes into the reviewer prompt
copy gitignored paths or globs copied into each worktree (legacy: .bunker/copy)
context repo files (read from the default branch) inlined into the implementer and reviewer prompts
models.implementer model for the implementer role
models.reviewer model for the reviewer role
models.auditor model for the auditor role
models.default model for every role without its own
skills.default skill directories (e.g. ~/.claude/skills/tdd) mounted read-only into ~/.claude/skills of every role; `skills = [...]` is the same
skills.implementer skill directories for the implementer role only
skills.reviewer skill directories for the reviewer role only
watch.interval minutes to sleep between bunker watch passes (default 10)
watch.parallel issues bunker watch works at once (default 1)
watch.max_cost_day USD cap per rolling 24h for bunker watch
watch.max_cost_month USD cap per rolling 30 days for bunker watch
watch.audit_every days between periodic bunker audit runs in bunker watch
watch.triage_every hours between periodic `bunker triage` runs in bunker watch (comment and close only)
merge.mode "manual": PRs stay open for a human to merge; bunker keeps them green (also written `merge = "manual"`) (default auto)
merge.ci_timeout minutes --merge waits for CI (default 30)
clean.logs_days days to keep .bunker/runs logs; bunker watch / bunker queue start-up and `bunker clean` prune older ones (default 30)
notify.enabled desktop notifications from bunker watch (notify-send / osascript) (default true)
notify.github_user login @-mentioned when an issue becomes bunker:failed / bunker-blocked (default: the repo owner, if a user)
Legacy .bunker/copy (same as copy in config.toml):
.bunker/copy gitignored files to copy from your checkout into each worktree before setup (e.g. .env):
one path or glob per line, `#` comments; directories are copied recursively, missing
paths warn. Paths outside the repo (`..`, absolute) are rejected. Copied files are never
committed (a commit that includes one fails the run) and the values of copied .env* files
are redacted from output. Library option: copyToWorktree: string[] overrides the file
Models:
Per role (implementer, reviewer, auditor), highest first: role flag (--implementer-model, --reviewer-model,
--auditor-model) > --model > role env (BUNKER_IMPLEMENTER_MODEL, BUNKER_REVIEWER_MODEL, BUNKER_AUDITOR_MODEL)
> BUNKER_MODEL > Claude Code's own default (no --model is passed). Env vars follow the Auth precedence below,
so a repo's .bunker/.env can set its own defaults; models.* in .bunker/config.toml rank below the env vars. The model in use is shown when each role starts, in the run
log, the PR body and --ui; "Claude default" when none is set.
Auth:
Easiest: run `bunker init` in the repo (or `bunker doctor` to just check). By hand:
Put CLAUDE_CODE_OAUTH_TOKEN (from `claude setup-token`) in ~/.config/bunker/.env,
plus optionally BUNKER_GH_TOKEN, a scoped token for bunker's own gh/git calls.
Precedence: exported variables > <repo>/.bunker/.env > ~/.config/bunker/.env.
--no-sandbox skips the check; claude on the host uses your normal login.
Examples:
bunker "add a greet() helper"
bunker task "add greet()" "add farewell()"
bunker review "add a greet() helper" --max-rounds 3
bunker issue 12 --verify "bun test"
bunker queue --verify "bun test && bun run typecheck" --merge
bunker watch --for 5h --max-cost-day 10
bunker ui
bunker triage
bunker triage --yes
bunker audit --focus "error handling"