CLI Reference
Chief provides a minimal but powerful CLI. All commands operate on the current working directory's .chief/ folder.
Usage
chief [command] [flags]Available Commands:
| Command | Description |
|---|---|
| (default) | Run the Ralph Loop on the active PRD |
start | Launch the TUI and start the Ralph Loop immediately |
new | Create a new PRD in the current project |
edit | Open the PRD for editing |
status | Show current PRD progress |
list | List all PRDs in the project |
worktree | Run a PRD worktree's setup or teardown command by hand |
Commands
chief (default)
Launch the TUI dashboard for the active PRD. This opens Chief in Ready state—press s to start the Ralph Loop, which then reads your PRD, selects stories, and invokes the agent iteratively.
chief [name]Arguments:
| Argument | Description |
|---|---|
name | PRD name to run (optional; when omitted, Chief auto-detects — see below) |
Flags:
| Flag | Description | Default |
|---|---|---|
--max-iterations <n>, -n | Maximum loop iterations | Dynamic |
--no-retry | Disable auto-retry on agent crashes — and waiting out a rate limit window | false |
--verbose | Show raw agent output in log | false |
--headless | Run without the TUI, logging to stdout — see below | false |
--worktree | With --headless, run in the PRD's own worktree | false |
--log-to-branch | Commit the run's log next to the PRD, so it travels with the branch instead of with the machine. chief box sets it for every run it starts | false |
Examples:
# Run with auto-detected PRD
chief
# Run a specific PRD by name
chief auth-system
# Increase iteration limit for large PRDs
chief --max-iterations 200
# Combine flags
chief auth-system -n 50 --verboseDynamic iteration limit
This is a global runaway backstop only. Chief primarily limits work per story — a story that fails 5 times is parked as needs-review and the loop continues with others. When --max-iterations is not specified, the global limit is calculated dynamically from the remaining stories and their per-story attempt budget, so it rarely fires first. You can adjust it at runtime with +/- in the TUI.
PRD auto-detection
When you run chief without a name, it resolves the PRD in this order:
- An explicit
--prdpath, if given - The PRD matching your current branch — if you're on a
chief/<name>branch and.chief/prds/<name>/exists, that PRD is opened (Chief printsUsing PRD "<name>" inferred from current branch chief/<name>). Since a run works on achief/<name>branch per PRD, running barechieffrom that branch lands you right back on the PRD you were working on. - A PRD named
default(or legacymain) - The only/first PRD found in
.chief/prds/ - First-time setup, if no PRD exists yet:
chief setupwhen the project has no.chief/config.yaml, then the first PRD's name
The same chief/<name> branch inference also applies to chief edit and chief followup.
A PRD whose run works in a worktree keeps its working files there, and that is the copy chief edit, chief followup, chief status and chief list read and write — the project's would be the state the run started from. chief edit and chief followup say so before they launch, and name the path. The follow-up inbox is the exception, because it is the one file you write by hand: yours is ingested wherever the run sits, and comes back ticked off.
chief start
Same as the default command, but starts the Ralph Loop immediately instead of opening in Ready state. Skips the manual s keypress—useful for scripts, CI, or when you just want Chief to get going.
chief start [name]Accepts the same arguments and flags as the default command (name, --max-iterations, --no-retry, --verbose, --agent, --model, ...).
Examples:
# Start the auto-detected PRD immediately
chief start
# Start a specific PRD by name
chief start auth-system
# Start with a custom iteration limit
chief start auth-system -n 50Branch safety
If you're on a protected branch (e.g. main) or another PRD is already running in the same directory, Chief still shows the branch/worktree confirmation before starting.
Prepared first. Before it starts the loop, chief start checks that the PRD was gone through with chief prep for this machine, as it stands now. When it never was, was prepared only for the box, or has changed since, the prep starts right there; once you exit it, chief start asks Start the run now? [Y/n] — leaving the session early looks exactly like finishing it — and only a yes (or Enter) starts the run. In a PRD worktree it checks and prepares the worktree's copy of the PRD, which is the one the run reads. When the stamp is fresh but every story left is blocked, the prep starts as well, to release them. A PRD in which nothing can run on this machine even then — everything done, blocked, parked, or needing another system — ends the start with a line saying which. --skip-prep starts without the check. A headless run (--headless, which is how the box runs) never checks and never preps; neither does anything without a terminal, which is told to run chief prep first instead of waiting for an answer. Starting the loop from inside the TUI with s does not check either.
Headless runs
--headless runs a PRD to completion with no TUI at all. It exists for the machine nobody is sitting at — a server reached over SSH, a cloud instance created for one run — where the alternate screen has nothing to draw into and nobody to ask.
chief start auth-system --headlessWhat changes:
- Output is a log, not a screen. One timestamped line per thing worth knowing: which story started, how it ended, reviews, retries, rate limit pauses, what the run cost.
--verboseadds the agent's narration and every tool call — useful when debugging a run, overwhelming over five hours. - Nothing is ever asked. The branch dialog does not appear, so the decision it makes is made by rule instead:
--worktreeruns in the PRD's own worktree (branchchief/<name>, withworktree.setuprun inside it), and without it the run works in the current checkout. Starting from a protected branch is not an error — Chief cuts and checks outchief/<name>first, because an unattended agent committing ontomainis the thing that must not happen. - It survives the terminal. Under
nohup,systemdortmux, a dropped SSH session does not touch the run. - SIGINT and SIGTERM stop it cleanly. The agent is killed, its commits stay on the branch, and the post-completion actions are skipped — an interrupted run has not finished, so nothing is pushed on its behalf.
- The exit code is the verdict.
0when every story is resolved,1otherwise, which is what a script wrapping the run needs.
onComplete works exactly as it does interactively: a finished run writes its summary, pushes, and opens the pull request if the project asks for it. Each is best-effort and reported — a push that fails for want of credentials is a line in the log, not a failed run.
# On a server, detached, logging to a file
nohup chief start auth-system --headless --worktree > run.log 2>&1 &
# In CI, where the exit code decides
chief start auth-system --headless || echo "stories left over"Running it on a throwaway cloud box
chief box creates a Hetzner instance for one run, starts the run on it, and tears it down when you are done.
chief new
Create a new PRD in the current project. This command launches the agent CLI with a preloaded prompt to help you define your project requirements interactively.
chief new [name] [context]Arguments:
| Argument | Description |
|---|---|
name | PRD name (optional, defaults to default). Must contain only letters, numbers, hyphens, and underscores. |
context | Additional context to pass to the agent (optional). Included in the PRD creation prompt. |
How it works:
- When the agent is Claude, Chief first shows a model picker (see below) — pick which Claude model drives the session
- Chief leaves git alone — you stay on whatever branch you're on. Branching is the run's job: the
chief/<name>branch is created when you start the PRD, which is also where the worktree option is offered. Authoring a PRD on your default branch is therefore fine and keeps that choice open - Chief launches the agent CLI with a specialized PRD-creation prompt. For Claude the session runs with
--dangerously-skip-permissions, so it can read the repo and writeprd.mdwithout a permission prompt on every step - The agent first asks, in plain prose, what you want to build and why — before touching your codebase (it does not guess the feature from the PRD name). If you passed
context, it plays that back to confirm instead of asking - Only once the goal is clear does it explore the repository, then grill you in rounds: it maps the decisions as a tree and asks the whole frontier of currently-answerable questions at once — each one rendered as
❓ **Q1** - **Title**: …followed by➡️its recommended answer — waits for your batch of answers, then recomputes the frontier for the next round. No native multiple-choice picker, just plain prose you can confirm wholesale ("all your recs") or redirect question by question. Facts it needs are looked up rather than asked, and a lookup still running only holds back the questions that depend on it. Codebase exploration always runs on Opus (via a subagent), independent of the model you picked for the session — so a lighter session model (e.g. Fable) speeds up the conversation without degrading how well the repo is understood - After you confirm the shared understanding, the agent writes
prd.mdin one pass - When done, type
/exitto leave the agent session - Chief validates the
prd.mdcan be parsed - Chief scaffolds an empty
todos.mdfollow-up inbox next toprd.md, ready for the fixes and polish items you collect after the feature is built (fed tochief followup). An existing inbox is never overwritten
Model picker (Claude only):
Before the session starts, Chief opens an interactive picker so you can choose the model for this PRD session:
- Default — no
--modelis passed; the Claude CLI uses its own configured model - Opus, Sonnet, Haiku, Fable — passed to the CLI as
--model <alias> - Custom… — type any model ID (e.g.
claude-opus-4-8)
The chosen model is passed to the Claude CLI via --model. The picker is skipped when you pin a model explicitly with the --model flag, and it does not appear for non-Claude agents. Pressing Esc cancels without creating the PRD.
The chosen model drives the conversation (grilling and PRD writing). Codebase exploration is always delegated to an Opus subagent regardless of this choice, so picking a lighter model like Fable trims cost on the back-and-forth without weakening how well Chief reads your repository.
What it creates:
.chief/
└── prds/
└── <name>/
└── prd.md # Structured PRD (written with the agent)Examples:
# Create a new PRD (defaults to name "default")
chief new
# Create a named PRD
chief new auth-system
# Create a PRD with additional context
chief new auth-system "We use Express.js with JWT tokens"
# The agent opens - describe what you want to build
# Type /exit when done - Chief generates the PRD filesINFO
Run chief new from the root of your project. Chief creates the .chief/ directory if it doesn't exist.
chief edit
Open an existing PRD for editing via the agent CLI.
chief editLaunches the agent with your PRD loaded, allowing you to refine requirements, add stories, or update prd.md conversationally. When you /exit, Chief validates the updated prd.md can be parsed.
The PRD it opens is the one being worked from: a run in a worktree keeps its own copy, and editing the project's would change a file the run never reads. Chief says so and names the path before the session starts.
Like chief new, this shows the Claude model picker before the session starts (Claude only; skipped when --model is set).
Arguments:
| Argument | Description |
|---|---|
name | PRD name to edit (optional, auto-detects if omitted) |
Examples:
# Edit the auto-detected PRD
chief edit
# Edit a specific PRD
chief edit auth-systemchief followup
Convert a PRD's follow-up inbox into new user stories.
chief followupAfter a PRD is implemented, you often find small fixes and polish items while reviewing the finished work by hand. Drop them as a flat markdown checklist into a todos.md (or followups.md) file inside the PRD's directory:
- [ ] Media card should be hidden when no media is attached
- [ ] Add a download button to the media view
- [ ] "In use" badge counts the same post twicechief followup launches the agent, which reads that inbox plus the existing prd.md, and turns each open (- [ ]) item into a proper ### US-XXX story appended to prd.md:
- New stories get the next available sequential ID (no separate range) and start as
**Status:** todo, so the normal loop picks them up next — completed stories are skipped by story selection. - When a follow-up refines an already-
donestory, its**Blocked by:**records that story's ID as a lineage note (a done blocker never holds the story back). - Existing
donestories are never reopened, keeping the one-commit-per-story history intact. - Converted items are flipped to
- [x]in the inbox with their new story ID, so re-running is idempotent.
Write the inbox in your normal checkout, whatever the run is doing. A PRD running in a worktree keeps its own copy of every PRD file, but the inbox is the one you fill in by hand: yours is carried over for the ingest, the stories are appended to the copy the run reads, and the ticked-off list is written back to the file you keep open.
Like chief new/chief edit, this shows the Claude model picker before the session starts (Claude only; skipped when --model is set). It only grills you about items whose intended behavior is genuinely ambiguous — clear items are converted directly.
Arguments:
| Argument | Description |
|---|---|
name | PRD name whose inbox to ingest (optional, auto-detects if omitted) |
Examples:
# Ingest the auto-detected PRD's todos.md
chief followup
# Ingest a specific PRD's inbox
chief followup auth-systemchief prep
Go through a PRD with you before an unattended run on this machine.
chief prep [name]
chief box prep [name] # the same, for a run on the boxA run that nobody watches stops on things nobody at the keyboard would have noticed: a question the PRD leaves open, two criteria that contradict each other, a keychain dialog, a tool that is not installed. chief prep starts a session that looks for exactly those, while you are still there to answer. It only edits prd.md (and, for the box, the box: section of .chief/config.yaml), never code, and it asks only for decisions — facts it looks up itself.
- Blocked stories first. For every
blockedstory it shows the reason and asks whether it is dealt with; if so the story goes back totodoand its**Blockiert (Ben):**line is removed. - The target.
chief preplooks at this machine — its OS and the tools the open stories need.chief box prepis told exactly what the box is built with: the Ubuntu base, what Chief reads from the project (runtimes, database, Redis, Meilisearch, browser libraries) and thebox.*settings. - The open stories. Decisions, contradictions (corrected in the text once you answer), hardware, permissions, accounts, keychain and signing, manual steps. It asks only what is really open; with nothing open there is no round of questions at all.
- What cannot run there gets a
**Braucht:**line. It proposes splitting such a story — the code runs anywhere, only the check needs macOS, say — and moves the dependencies over so the stories after it keep running, changes priorities only where a dependency needs it (not to put the target's stories first, which would flip with every prep for the other target), and says how many stories can run there. - Missing tools. For the box, apt packages go into
box.packages; a service is switched on through.env, which it tells you rather than edits. Locally it installs nothing unasked: it offers the command (brew install …) and runs it only after your yes. - "Vor dem Start noch:" — a checklist of what does not belong in the PRD: unlock 1Password, log in somewhere, plug something in.
When you exit, Chief commits the changed prd.md and config (unless you keep .chief/ out of git) and pushes the branch, then records that the PRD is prepared for this target in prep.json next to it. The stamp is local to the checkout and ignored by git; it keeps a fingerprint of the PRD without its status lines, blocked reasons and ticked boxes, so a run working through the stories does not make it stale — editing what a story asks for does. A prep for the box and one for this machine are recorded separately.
chief start and chief box up run the matching prep themselves when the stamp is missing or stale, or when everything left is blocked, and ask before they start after it (see chief start). Prep needs a terminal: it never runs headless or on the box.
Like chief edit, this shows the Claude model picker before the session starts (Claude only; skipped when --model is set).
chief status
Show progress for the current PRD. Displays a summary of story completion at a glance.
chief statusOutput includes:
- The project name (from the PRD's top-level heading)
- An
X/Y stories completecount - A list of the incomplete stories, with
(in progress)next to the one currently being worked on (orAll stories complete!when none remain)
The progress reported is the running one: a PRD whose run works in a worktree keeps its working files there, and that is the copy read — the project's would still show the state the run started from. The same holds for chief list.
Examples:
# Check progress on the auto-detected PRD
chief status
# Example output:
# My Auth Project
# 5/8 stories complete
#
# Incomplete stories:
# US-006: Password Reset Flow (in progress)
# US-007: Account Deletion
# US-008: Two-Factor Authchief setup
Walk through the settings a project needs once and that are easy to forget, then write them to .chief/config.yaml.
chief setupIt asks, in this order:
- Worktree setup command: the command every new worktree runs before the agent starts. Proposed from the project:
cp "$CHIEF_REPO_DIR/.env" .envwhen there is an.env, thencomposer install --no-interaction, the install for whichever JavaScript lockfile is there (pnpm,yarn,bun,npm ci),uv syncorpoetry install,bundle install - Push the branch? Open a pull request? when a run finishes. A pull request is only asked when pushing is on, and if
ghis missing or not logged in it says so and turns pull requests off unless you insist - MCP servers an unattended iteration starts with:
inherit(everything this machine has),project(only.mcp.json),none, or a JSON file of your own. Seeagent.mcpin the configuration reference - Skills: whether build iterations load the skill catalogue (
agent.skills) - Review and consolidation: on or off, each with an optional skill
- Cloud box (optional): sets up the credentials with
chief box tokenwhen none are stored, opens the location and server type picker fromchief box config, and asks which untracked files to copy (.envby default)
Every question opens on what the project uses now, or on the detected value for a project without a config, so Enter keeps it and - clears a text answer. Nothing is written until the summary at the end, and only after a yes; Ctrl+C or a closed input leaves the file untouched.
Running bare chief in a project with no PRD and no .chief/config.yaml runs chief setup first, then asks for the first PRD's name. Run it again any time; everything it does not ask is in the Settings TUI (,).
chief list
List all PRDs in the current project.
chief listScans .chief/prds/ and shows each PRD with its completion status.
Examples:
# List all PRDs
chief list
# Example output (format: <name>: <title> (<completed>/<total>, <pct>%)):
# auth-system: My Auth Project (5/8, 62%)
# landing-page: Marketing Site (12/12, 100%)
# api-v2: Public API (0/6, 0%)chief box
Runs a PRD on a throwaway cloud instance, so a run that takes hours does not take your machine with it. The box is created for the run and destroyed when you are done; at current Hetzner prices a five-hour run costs under thirty cents of computer — under five if it is happy on two cores — which is two orders of magnitude less than the tokens it spends.
For the whole picture — why a box, what it is built with, what leaves it, and how it switches itself off — see Running on a Box.
chief box config # pick the location and the machine size, once
chief box prep <prd> # go through the PRD with you for a run on the box
chief box up <prd> # create the box, put the project on it, start the run
chief box run <prd> # the same, then follow the log and say when it ends
chief box retry # put the project on the box that is already there
chief box logs # follow the running box's log
chief box status # what it is doing, and how long it has been billing
chief box list # every box you have, and what each has cost
chief box ssh # a shell on the box, in the project directory
chief box down # destroy it — this is what stops the billingFlags for up and run:
| Flag | Description | Default |
|---|---|---|
--keep | Leave the box standing when the run ends, instead of letting it destroy itself | box.keep, else false |
--max-hours <n> | From this long after booting, the box stops a run that has not committed in six hours and destroys itself; a run that is still committing goes on, up to four times this | box.maxHours, else 12 |
--at <HH:MM> | Set the box up now and start the run at this time — the next time the clock reads it, in your time zone. The outside limit then counts from the start (also on retry) | start at once |
--down-when-done | Wait for the run to end, then destroy the box at once, rather than after its own grace period (run only) | false |
--worktree | Run in the PRD's own worktree on the box | On when the project configures worktree.setup |
--verbose | Put the agent's narration in the box's log | false |
--max-iterations <n>, -n | Cap the run's iterations | Dynamic |
--type <name> | Hetzner server type | box.type, else the cheapest the location sells |
--location <name> | Hetzner location | box.location, else fsn1 |
--image <name> | Hetzner image | box.image, else ubuntu-24.04 |
--php <series> | PHP to install, e.g. 8.3 | box.php, else what this machine runs |
--node <major> | Node to install, e.g. 22 | box.node, else what this machine runs |
--file <path> | An untracked file the run needs; repeatable | .env |
--package <name> | An apt package to install on top of what was read from the project; repeatable | — |
--skip-prep | Create the box without checking that chief box prep has gone through the PRD as it stands | check, and prep first if needed |
Flags for down:
| Flag | Description |
|---|---|
--force | Destroy without asking about commits the box never pushed |
--all | Destroy every box in your Hetzner project, not just this project's |
--name <name> | Destroy one box by name, whichever checkout created it |
The box switches itself off
A box destroys itself once its work is somewhere else. When the run finishes successfully a timer starts, and twenty minutes later a script on the box pushes anything still only there, checks that nothing is, and deletes the machine through the Hetzner API. From --max-hours after boot (twelve by default) the box checks on its run every hour: one that has not committed in six hours is stopped and the same script runs — the backstop for a run that hangs rather than ends. A run that is still committing is left to finish, up to four times --max-hours after it started.
Two things it will not do. It never destroys a machine still holding commits that exist nowhere else: it says so in the journal and tries again in an hour. And a run that ended with stories unresolved does not start the first timer at all, because that is the box you want to chief box ssh into or chief box retry — it lives until the deadline instead.
With --at the box is set up at once and the run waits for the time you gave: the box starts it from a systemd timer, so nothing on your machine has to be awake for it, and --max-hours is then counted from the start rather than from boot. The wait bills like any other hour of the box. See Starting later.
--keep builds the old behaviour, where chief box down is the only thing that stops the bill. The Hetzner token has to be on the box for any of this, root-only and never in the cloud-config; keep chief's boxes in a Hetzner project of their own, and see Running on a Box for what that does and does not protect.
A box that destroyed itself leaves its record behind in the checkout. up, logs and status notice the machine is gone, say so, and clear it.
What the run leaves behind
A box run commits its log next to the PRD (run-<date>-<time>.log) and pushes it with the branch, alongside the run summary. It pushes whatever the project configured: onComplete.push is a setting about a laptop, where the commits are still there in the morning either way, and on a machine created to be destroyed that default is simply wrong. The log says when it went against your config. This is not a preference: the box writes its log into its own systemd journal, and the journal dies with the machine — often overnight, destroyed by the same --down-when-done that waited for the run. The branch is the only thing that outlives the box, so that is where the log goes. It is force-added, so it lands even in a project that ignores .chief/.
The log is committed before the push, and therefore stops just short of it: a push that worked is visible on the remote, and a push that failed did not carry the commit anywhere anyway.
When a step fails
up stops at the first failed provisioning step rather than reporting a half-built box, and the box stays up so it can be looked at. If what failed came after provisioning — a clone refused by an expired token, a file that was not where it was said to be — chief box retry puts the project on that same box and starts the run, instead of paying to build an identical machine. Every step it repeats is written to be repeated. A run that is already going is left alone.
Choosing where the box runs
chief box config asks two questions and writes the answers to box.location and box.type in .chief/config.yaml. The list it offers comes from the Hetzner API rather than from a table inside chief, so it shows what is actually bookable today, in that location, at that location's price — including what five hours of each machine would cost.
Location is asked first because it is the decision that matters. The box holds your source, your .env and a token that can push to the repository for the length of the run; which country that happens in is not a performance setting.
The screen opens by itself on the first chief box up in a project that has never been asked — but only at a terminal, and only when no --location or --type already answers it. In a script or a background shell there is nobody to ask, and up falls through to the defaults as before.
Finding a box you forgot
chief box listEvery server in your Hetzner project labelled managed-by=chief, with its age and what it has cost so far. logs, status and down all work from a record inside one checkout, and a box is forgotten exactly when that record stops being consulted — the branch was deleted, the laptop reinstalled, the run started three weeks ago from a directory nobody has opened since. The machine bills regardless, and list is the only command that asks Hetzner instead of the checkout.
chief box down --all is what does something about the list; --name picks one out of it. Both ask once, up front, naming the boxes that hold commits nobody pushed.
Being told the run ended
chief box run follows the log until the run actually ends, then says how it went and — with onComplete.notify on — sends a desktop notification on this machine. Both run and logs reconnect when the connection drops and resume exactly where they left off, so a laptop that slept for a minute does not end the log where it would otherwise simply stop moving. That is a different thing from the notification chief already had: that one fires wherever the run happens, which for a box is a server with no display, where it reaches nobody.
So the ping arrives when your machine is awake and still watching, which is the only case a banner is any use in. A laptop that was asleep is what the assigned pull request is for the next morning. And a run that is still going when you hit Ctrl-C leaves the box up, untouched, with no notification to give.
chief box run --down-when-done closes the same gap from the other end: it follows the log, waits for the run to actually finish, and then destroys the box. It is not forced — a run can commit without pushing, and down still asks about that. Ctrl-C leaves the box up, because nobody asked for a machine to be destroyed by closing a terminal.
A refused server type
Hetzner's server types are generational: a line stops being bookable in a location when its successor arrives, while still appearing in the price list. If up fails with "unsupported location", the error lists the types that location actually offers — pick one with --type.
What happens on up
- Reads the project and prints what it found, before anything exists to pay for. See what the box is built with.
- Builds chief for the box from the checkout you are running, so the box runs the chief you have rather than the last one that was released.
- Creates the instance and provisions it with what step 1 read, plus the GitHub CLI and Claude Code from its signed apt repository.
- Clones the project from
origin, on the branch you are standing on. - Copies what git does not carry — the PRD,
.chief/config.yaml, and the files named by--file. The.envis translated on the way, see below. - Starts the run as a systemd unit, so it survives every dropped connection after that.
Because step 4 is a clone rather than a copy of your working tree, up asks origin about your branch before it creates anything, and refuses a run whose starting point the box would never see: a branch that was never pushed (the clone would quietly land on the default branch instead) or one that is ahead of origin (the run would start without your last commits). Both say git push and cost nothing to fix.
Only the machine is provisioned. Everything specific to a checkout — its dependencies, its schema, its build — belongs in that project's worktree.setup, which runs inside the checkout before the agent starts.
If a provisioning step fails, up says so within seconds rather than reporting a box that is missing a tool. cloud-init would otherwise carry on past a failing line — a download that answered 504, a package that does not exist for the PHP series — and mark the box ready anyway; the first box built this way did exactly that. The cloud-config stops at the first failure and leaves a chief-failed marker instead of the ready one, up prints the last lines of the provisioning log, and the box is left running for you to look at (chief box ssh, then cat /var/log/cloud-init-output.log) and to destroy with chief box down. Downloads of binaries retry a few times first, so a single bad answer from a CDN does not fail the box.
What the box is built with
There is no fixed list. Before creating anything, up reads the project and decides from that:
| Read from | Decides |
|---|---|
composer.json | That there is PHP, and whether it is a Laravel app. Every ext-* it requires becomes a php<series>-* package. |
composer.lock | The ext-* every dependency requires, transitively — the ones composer install would otherwise fail on. |
Herd, then php on your PATH | The PHP series, so the box runs what you run. A site Herd has isolated to a version gets that version; otherwise the php in the project directory answers. Without either, composer.json's platform pin, then the lowest version its constraint accepts. |
.env (or .env.example) | The database server — PostgreSQL, MariaDB or nothing for SQLite — from DB_CONNECTION, and the database to create from DB_DATABASE. Redis when any store is set to it (or Horizon is installed). Meilisearch when SCOUT_DRIVER says so. |
phpunit.xml | The driver the tests use, which is often not the one the app does; both get installed. |
node on your PATH, then .nvmrc, then engines | The Node major, the same way as PHP. |
| The lockfile | The package manager: bun.lock, pnpm-lock.yaml, yarn.lock, else npm. A packageManager field in package.json pins the version. |
| Dependencies | Browser tests: Pest's browser plugin, Playwright, Puppeteer or Browsershot bring Chromium's system libraries; Dusk brings Google Chrome. |
go.mod | The Go toolchain, and none of the above. |
PHP comes from Ondřej Surý's PPA, which is why the image is Ubuntu 24.04 rather than the newest LTS: it is the one release every series from 8.0 to 8.5 is packaged for under the same names. --php and --node (or box.php and box.node) pin a version when the detection is wrong for a project, and --package adds what no manifest declares.
The .env is pointed at the box. It is copied because a Laravel app cannot run without it, but it was written for your machine — Herd's or DBngin's database, on your host, as your user. On the box the same database exists, created from what was read out of that file, under the box's own account (chief, password chief). So DB_HOST, DB_PORT, DB_USERNAME and DB_PASSWORD are rewritten to the box's; REDIS_HOST and MEILISEARCH_HOST likewise when those are in use; an absolute SQLite path becomes the project's database/database.sqlite. Nothing else in the file is touched, and up lists the keys it changed.
Credentials
Chief works these out rather than asking you to configure them:
- GitHub comes from
gh auth token. Anyone who can open a pull request from this machine already has a token that works. - Claude comes from
claude setup-token, which chief runs for you the first time a box needs one. It opens a browser once; the result is saved to your config directory and reused for every run after that. - Hetzner is the one thing to put in place by hand, because nothing can mint it: create a Read & Write API token in the Hetzner console, then save it to
<config>/chief/hetzner-tokenor setCHIEF_BOX_HETZNER_TOKEN. An existinghcloudCLI context is read too, so a machine that already has that set up needs nothing.
Your SSH key is registered with Hetzner automatically if the project does not have it yet, matched by fingerprint so an existing key is reused rather than duplicated.
How the box is protected
A firewall, attached as the server is created. Inbound: SSH and ping, from anywhere. Nothing else. SSH stays open to the world rather than pinned to your address — an address that changes between creating a box and looking at it locks you out of your own machine for no gain, since the port is protected by keys. What the firewall does close is everything else: a worktree.setup that starts a dev server, a queue dashboard or a database listening on all interfaces would otherwise be on the public internet for the hours the run takes. Outbound is untouched.
There is one firewall, named chief, shared by every box. It is created the first time you need one and reused forever after, so nothing has to be deleted when a box is destroyed — which matters, because Hetzner detaches a firewall asynchronously and a delete issued straight after the server's always comes too early. If its rules have been edited in the console, chief box up puts them back and says so: the firewall is chief's, and a box silently missing the protection chief promises is worse than no firewall at all. A firewall without chief's managed-by=chief label is never read or touched.
A host key chief generated before the machine existed. It is created locally, put into the instance through cloud-init, and checked on every connection. Without it the first connection — the one carrying the Claude token, the GitHub token and your .env — would have to trust whatever answered at the address. The key lives in .chief/box/known_hosts, per project rather than in your own ~/.ssh/known_hosts, so a throwaway address that Hetzner recycles within hours never lands in the file guarding your real servers. It is deleted along with the box.
What the box is trusted with
The box holds a token that can act as you on GitHub and one that can spend your Claude subscription, and the agent runs on it with --dangerously-skip-permissions. A fine-grained GitHub token scoped to the one repository is a better fit here than a classic token with repo, and chief box up says so when the token it found reaches further than the project — gh auth token returns an account-wide one. Set CHIEF_BOX_GH_TOKEN to narrow it.
A box nobody destroyed bills until somebody does. chief box down stops it, chief box list finds the ones you forgot, and chief box run --down-when-done avoids the situation.
chief worktree
Run a PRD worktree's worktree.setup or worktree.teardown command by hand, without starting a run.
chief worktree setup <prd>
chief worktree teardown <prd>Both subcommands resolve the worktree exactly the way the TUI does: the branch is chief/<prd>, and the directory comes from the worktree.dir template (default .chief/worktrees/<prd>). The command runs in that directory with the same CHIEF_* environment variables a run would give it, so a script cannot tell the two apart.
Output is streamed to your terminal while the command runs and written to <worktree>/.chief/prds/<prd>/setup-<timestamp>.log or teardown-<timestamp>.log at the same time — the same log files a run produces, in the same place: a run keeps the PRD's working files inside its worktree, and neither subcommand removes the worktree, so the log stays readable where it belongs. The path is printed when the command finishes.
chief worktree setup is the way to re-run a setup after fixing the script, or after configuring worktree.setupOnReuse: false. chief worktree teardown does not remove the worktree — it only runs the command, which makes it the way to retry a teardown that failed, or to drop a worktree's outside resources while keeping the checkout. Removing a worktree stays the job of the TUI's clean flow (c on a finished PRD), which runs the teardown itself.
Both commands fail with a non-zero exit status when:
- the PRD has no worktree at the resolved path (nothing is run — start the PRD in chief to create it),
- no
worktree.setup/worktree.teardowncommand is configured in.chief/config.yaml, - the command itself exits non-zero, or a setup outlives
worktree.setupTimeoutSeconds(that key bounds the setup only — a teardown is never killed).
Arguments:
| Argument | Description |
|---|---|
setup | teardown | Which configured command to run (required) |
prd | PRD name whose worktree to run it in (required) |
Examples:
# Re-run the setup script in the auth PRD's worktree
chief worktree setup auth
# Drop the auth worktree's database and containers, keep the checkout
chief worktree teardown authKeyboard Shortcuts (TUI)
When Chief is running, the TUI provides real-time feedback and interactive controls:
Loop Control
| Key | Action |
|---|---|
s | Start the loop (when Ready, Paused, Stopped, or Error) |
p | Pause the loop (finishes current iteration gracefully) |
x | Stop the loop immediately (kills agent process) |
View Switching
| Key | Action |
|---|---|
t | Toggle between Dashboard and Log views |
d | Toggle Diff view (shows the selected story's commit diff) |
PRD Management
| Key | Action |
|---|---|
n | Open PRD picker in create mode (switch PRDs or create new) |
l | Open PRD picker in selection mode (switch between existing PRDs) |
1-9 | Quick switch to PRD tabs 1-9 |
e | Edit current PRD (from any main view) |
m | Merge completed PRD's branch into main (in picker or completion screen) |
c | Clean worktree and optionally delete branch (in picker or completion screen). Runs worktree.teardown inside the worktree first; a non-zero exit keeps the worktree and asks |
Settings
| Key | Action |
|---|---|
, | Open Settings overlay (from any view) |
Navigation
| Key | Action |
|---|---|
j / ↓ | Move down (stories in Dashboard, scroll in Log/Diff) |
k / ↑ | Move up (stories in Dashboard, scroll in Log/Diff) |
Ctrl+D / PgDn | Page down (Log/Diff view) |
Ctrl+U / PgUp | Page up (Log/Diff view) |
g | Jump to top (Log/Diff view) |
G | Jump to bottom (Log/Diff view) |
+ / = | Increase max iterations by 5 |
- / _ | Decrease max iterations by 5 |
General
| Key | Action |
|---|---|
? | Show help overlay (context-aware) |
Esc | Close modals/overlays |
q | Quit (gracefully stops all loops) |
Ctrl+C | Force quit |
TIP
The TUI has three views: Dashboard showing stories and progress, Log streaming the agent's output in real time, and Diff showing the commit diff for the selected story. Press t to toggle Dashboard/Log, or d to open the Diff view.
Environment Variables
| Variable | Effect |
|---|---|
CHIEF_AGENT | Agent CLI to use: claude, codex, opencode, cursor, or gemini. Overridden by --agent. |
CHIEF_AGENT_PATH | Custom path to the agent CLI binary. Overridden by --agent-path. |
CHIEF_MODEL | Model passed to the Claude CLI via --model. Overridden by --model. |
NO_COLOR | Any non-empty value strips all colors/styling from the TUI (no-color.org). |
CHIEF_ASCII | Truthy value (1/true/yes/on) replaces emoji/Unicode icons with ASCII fallbacks, for terminals that render them poorly. |
CHIEF_BOX_HETZNER_TOKEN | Hetzner Cloud API token for chief box. Overrides the one chief box token stored. |
CHIEF_BOX_GH_TOKEN | GitHub token the box clones and pushes with, when gh auth token's account-wide one is more than the project should get. |
CHIEF_BOX_SSH_KEY | Path to the public key registered with Hetzner for the box, when it is not ~/.ssh/id_ed25519.pub, id_ecdsa.pub or id_rsa.pub. |
CHIEF_SRC | Path to a chief checkout, for building the binary the box runs when chief box is started from a project that is not chief itself and the checkout is not in one of the usual places. |
Agent resolution order: --agent / --agent-path / --model flags → CHIEF_AGENT / CHIEF_AGENT_PATH / CHIEF_MODEL → .chief/config.yaml → default claude. See Configuration → Appearance for the TUI appearance variables.
Exit Codes
Chief uses standard exit codes:
| Code | Meaning |
|---|---|
0 | Success |
1 | Error |