Configuration
Chief uses a project-level configuration file at .chief/config.yaml for persistent settings, plus CLI flags for per-run options.
Config File (.chief/config.yaml)
Chief stores project-level settings in .chief/config.yaml. This file is created by chief setup (which also runs on first launch) or when you change settings via the Settings TUI.
Format
agent:
provider: claude # or "codex", "opencode", "cursor", or "gemini"
cliPath: "" # optional path to CLI binary
model: "" # optional model passed via --model (Claude only)
mcp: inherit # MCP servers a run may reach: inherit | none | path to a .mcp.json-shaped file
skills: inherit # skill catalogue in build iterations: inherit | none
worktree:
setup: "npm install"
setupOnReuse: true # run setup again when an existing worktree is picked up; omit = yes
teardown: "" # shell command run in a worktree right before it is removed
baseBranch: "" # branch worktree branches are cut from; empty = the detected default branch
dir: "" # where worktrees live; empty = .chief/worktrees/{prd}
setupTimeoutSeconds: 0 # kill a setup that runs longer than N seconds; 0 = wait forever
onComplete:
push: true
createPR: true
prBaseBranch: "" # branch PRs merge into; empty = the branch the run's branch was cut from
summary: true # write & commit a timestamped summary-<date>-<time>.md when the run finishes
notify: true # desktop notification when the run stops, finished or not
loop:
watchdogTimeoutSeconds: 300 # kill a silent agent after N seconds; 0 = default (5 min)
keepAwake: true # keep the machine awake while a loop is running (macOS)
review:
enabled: true # run the review with just the built-in prompt (no extra config needed)
model: "" # model the review agent runs on; empty = sonnet (Claude only)
skill: "/code-quality" # optional project skill the review agent runs (Claude only)
instructions: | # optional free-form guidance for the review agent
Watch for N+1 queries and make sure new behavior has tests.
consolidate:
enabled: true # one refactor pass over the whole run, after the last story
model: "" # model the consolidation agent runs on; empty = sonnet (Claude only)
skill: "/code-quality" # optional project skill the consolidation agent runs (Claude only)
instructions: | # optional free-form guidance for the consolidation agent
We keep all HTTP clients in internal/transport.Config Keys
| Key | Type | Default | Description |
|---|---|---|---|
agent.provider | string | "claude" | Agent CLI to use: claude, codex, opencode, cursor, or gemini |
agent.cliPath | string | "" | Optional path to the agent binary (e.g. /usr/local/bin/opencode). If empty, Chief uses the provider name from PATH. |
agent.model | string | "" | Optional model passed to the Claude CLI via --model. Needed when Claude Code's -p mode ignores ~/.claude/settings.json (e.g. local models via LM Studio). |
agent.mcp | string | "" (= inherit) | Which MCP servers an unattended iteration starts with. inherit (the default) keeps what every run did before this key existed: everything the machine has configured — the repository's .mcp.json, your own servers, and every account-level connector. none starts the agent with no MCP server at all (--strict-mcp-config). Anything else is a path to a JSON file in the same shape as .mcp.json, and then the agent sees exactly the servers named there and nothing else. A relative path is resolved against the project root, not the working directory, so a run inside a worktree finds the same file. The file is read when the run starts: a path that isn't there, a file that isn't valid JSON, and a file whose mcpServers object is empty each abort the start with a message naming this key — because the alternative is a run that quietly has no servers for an hour. Only the loop's own agents are affected; chief new and chief edit keep the full set, since a person is sitting in front of them. Claude-specific; other providers ignore it. |
agent.skills | string | "" (= inherit) | Whether Claude Code's skill catalogue (skills and slash commands) is loaded into build iterations. inherit (the default) loads it; none leaves it out (--disable-slash-commands), which keeps the catalogue out of the context of every turn rather than just the first. Review and consolidation always get it back, whatever this says — review.skill / consolidate.skill name a skill those passes run, and an empty catalogue would disable them without saying so. Claude-specific; other providers ignore it. |
worktree.setup | string | "" | Shell command to run in new worktrees (e.g., npm install, go mod download). Runs with the PRD name, branch and paths in its environment — see Worktree command environment. Write it to be idempotent: by default it also runs when Chief picks up a worktree that already exists, so it has to survive being run against a checkout it has already set up — npm install and go mod download do, createdb chief_$CHIEF_PRD_NAME does not. Either make the command tolerate a second run (`createdb … |
worktree.setupOnReuse | bool | (unset) | Whether worktree.setup also runs when Chief picks up a worktree that is already there — a run resumed after a crash or a PRD started again. Unset (the default) runs it, which is what every run did before this key existed. Set it to false when the setup is expensive or not idempotent; then only a freshly created worktree gets one, and the spinner shows the step as Skipped setup (worktree reused). A worktree that was standing on the wrong branch does not count as reused: it is torn down and recreated, so its replacement is set up like any new one. Leaving this unset means setup commands should be idempotent, since they will be run against checkouts they have already set up. |
worktree.teardown | string | "" | Shell command run inside a worktree right before Chief removes it, so resources that live outside git — a per-worktree database, a web-server link, containers — disappear with the directory instead of being orphaned. Runs on both clean options (c in the picker) and before a stale worktree on the wrong branch is recreated. A non-zero exit cancels the removal: the worktree is kept, the tail of the command's output and the path of its log file are shown, with the option to remove it anyway. Empty (the default) keeps removal a pure git operation. Make it idempotent — it may run against a worktree it already tore down. Gets the same environment as worktree.setup — see Worktree command environment. |
worktree.baseBranch | string | "" | Branch that worktree branches are cut from, and therefore the branch their pull request targets. Empty (the default) uses the repository's detected default branch (main/master); set it to develop in a repo where work doesn't start at main. Looked up locally first, then as origin/<branch>; a name that exists in neither aborts the start with a message naming this key, rather than branching off somewhere else. Reaches the run's setup and teardown commands as CHIEF_BASE_BRANCH — see Worktree command environment. |
worktree.setupTimeoutSeconds | int | 0 | Seconds a worktree.setup command may run before Chief kills it and the whole process tree it started. 0 (the default) waits forever, which is the right answer for a setup that is merely slow — a cold npm install on a big project takes minutes. Set it for the setup that hangs: one waiting on a lock, a password prompt or a network that never answers, which would otherwise block the run for good. A killed setup fails the worktree creation and names its log file. |
worktree.dir | string | "" | Path template saying where a PRD's worktree goes. Empty (the default) means .chief/worktrees/{prd}. Placeholders: {prd} (the PRD name), {repo} (the basename of the main checkout) and {branch} (the worktree's branch, with / replaced by -). A relative template is resolved against the main checkout, so ../{repo}-worktrees/{prd} puts worktrees beside the project — useful when a bundler, test runner or editor index trips over checkouts nested inside the checkout. A template resolving to the main checkout itself or to its bare parent directory is rejected before anything is created, with a message naming this key. Chief finds worktrees at the configured location again through git worktree list, so orphan detection and cleanup follow the template. A location inside the repository is added to the main checkout's .gitignore before the worktree is created — the directory holding the worktrees (.chief/worktrees/ for the default), not each worktree — unless git check-ignore says something already covers it. A location outside the repository, or one directly in its root, is left alone. |
onComplete.push | bool | false | Automatically push the branch to remote when a PRD completes. Only runs if the branch has at least one commit. |
onComplete.createPR | bool | false | Automatically create a pull request when a PRD completes (requires gh CLI). Only runs after a successful push, so a run with no commits creates no PR. The PR targets the branch the run's branch was cut from, and an already-open PR for the branch is reported instead of a second one being opened — see Pull request target. A newly opened PR is assigned to the account gh is authenticated as, so an unattended run — a box finishing overnight — lands in your "Assigned to you" list rather than among the branches. That is where it is found, not a notification: the run authenticates as the same account, and GitHub does not report your own actions back to you. Assigning is best-effort and never costs the PR. |
onComplete.prBaseBranch | string | "" | Forces the branch pull requests merge into. Empty (the default) lets Chief use the branch the run's branch was cut from. Set this only when that answer is wrong for your workflow. A branch origin doesn't have is ignored, leaving the choice to gh. |
onComplete.summary | bool | true | When a run finishes (or hits max iterations with committed work), run the agent once more to write a human-facing, timestamped summary-<date>-<time>.md next to the PRD — what was built, how to test it, where the new functionality lives, and open/parked follow-ups. Each run writes its own file (sortable name), so the PRD keeps a run history. The file is committed automatically (force-added, so it lands even when .chief/ is gitignored); with onComplete.push on, it rides along in the pushed branch/PR. Only runs when the branch has at least one commit. |
onComplete.notify | bool | true | Send a desktop notification when a run stops — finished, capped by maxIterations, or dead on an error it could not retry past (macOS osascript, Linux notify-send). |
loop.watchdogTimeoutSeconds | int | 0 | Seconds of agent silence (no output) before the watchdog kills the hung process. 0 uses the built-in default of 5 minutes. Raise it when the agent runs long, silent builds or test suites that would otherwise trip the watchdog. |
loop.keepAwake | bool | true | Stop the machine from going to sleep while a loop is running. A run is a walk-away workflow — nobody touches the keyboard for an hour — so the OS would otherwise idle-sleep the machine mid-story and leave the agent frozen until you came back. macOS only (caffeinate -i -s, so the display still sleeps and only the machine stays up); a no-op on other platforms. A running loop re-reads it within a few seconds, so switching it on halfway through a run applies to that run. A closed lid still sleeps a MacBook without an external display — that's a firmware behavior no assertion can override, so a run started on battery is warned about before it begins (see The Machine Fell Asleep Mid-Run); setting this to false raises the same warning. |
review.enabled | bool | unset (= on) | The hard switch, and it always wins. true runs the review with just the built-in prompt (the two-axis Spec/Standards review and code-smell baseline) — no skill or instructions needed. false keeps the review off even when review.skill or review.instructions are set, so you can park a skill in the config without it running. Left out entirely, the review runs. |
review.model | string | "" (= sonnet) | Model the review agent runs on, e.g. haiku, sonnet, opus. Left empty, the review runs on Sonnet rather than on the build agent's model: reviewing one story's diff is a large share of a run's cost and doesn't need the build model. Claude-specific (passed via --model); other providers ignore it. agent.model is untouched either way — if your build agent runs on a model of its own (e.g. a local model via LM Studio), set this to the same value so the review reaches it too. |
review.skill | string | "" | Name of a project skill (e.g. /code-quality) the separate review agent runs as part of its review. Claude-specific; other providers ignore it. Optional. |
review.instructions | string | "" | Free-form guidance for the review agent (e.g. "watch for N+1 queries and missing tests"). Works with any provider. Optional. |
Unless review.enabled is false, Chief spawns a separate agent with a fresh context after each story's build agent has committed. It never sees the build agent's reasoning, so it reviews the committed changes adversarially — like a colleague reviewing a PR rather than the author re-checking their own work. It fixes anything it finds and amends the story's commit (keeping one commit per story). The review is best-effort: a review crash is logged but does not un-complete the story.
See The Review Agent for the full picture — why it's a separate agent, how it slots into the loop, and how the review prompt is assembled from your skill/instructions.
| Key | Type | Default | Description |
|---|---|---|---|
consolidate.enabled | bool | unset (= on) | The hard switch, and it always wins. true runs one consolidation pass over the whole run after the last story finishes: a single agent looks across every commit the run landed and refactors away the seams that no per-story review can see. false keeps the pass off even when consolidate.skill or consolidate.instructions are set. Left out entirely, the pass runs. |
consolidate.model | string | "" (= sonnet) | Model the consolidation agent runs on, e.g. haiku, sonnet, opus. Left empty, the pass runs on Sonnet rather than on the build agent's model, for the same reason the review does. Claude-specific; other providers ignore it. Set it to the same value as agent.model when your build agent runs on a model of its own. |
consolidate.skill | string | "" | Name of a project skill (e.g. /code-quality) the consolidation agent runs as part of its pass. Claude-specific; other providers ignore it. Optional. |
consolidate.instructions | string | "" | Free-form guidance for the consolidation agent (e.g. "we keep all HTTP clients in internal/transport"). Works with any provider. Optional. |
box.location | string | "" (= fsn1) | Hetzner location a chief box run is created in, e.g. fsn1 for Falkenstein. The box holds the project's source and its .env for the length of a run, so this decides which country that happens in. Set it with chief box config, which lists what Hetzner actually offers today; --location overrides it for one run. |
box.type | string | "" (= the cheapest the location sells) | Hetzner server type for a box, e.g. cpx32 (4 vCPU, 8 GB). Left empty, every box is created on the cheapest machine its location offers that is not being retired, asked of Hetzner at the moment of creation. Set it with chief box config, which shows each machine's price and what five hours of it would cost; --type overrides it for one run. |
box.image | string | "" (= ubuntu-24.04) | Hetzner image a box is built from. Rarely worth setting: PHP comes from a PPA that publishes for this release, and a newer one may have no PHP at all. |
box.php | string | "" (= detected) | PHP series the box installs, e.g. 8.3. Left empty, chief uses the version Herd isolates this site to, else the php on this machine, else the lowest composer.json accepts. --php overrides it for one run. |
box.node | string | "" (= detected) | Node major the box installs, e.g. 22. Left empty, chief uses the node on this machine, else .nvmrc, else the lowest package.json accepts. --node overrides it for one run. |
box.packages | list | [] | Apt packages this project needs on the box, on top of what chief read from the project. For the dependency no manifest declares. --package adds to the list for one run. |
box.files | list | [".env"] | Paths, relative to the project, that git does not carry but the run needs. --file replaces the list for one run. |
box.keep | bool | false | Leave a box standing after its run ends. By default a box destroys itself once its work is on origin — the alternative is a machine billing until somebody notices, and the person who would notice is asleep, which is the whole reason the run is on a box. Set this for a project whose boxes you want to inspect afterwards, and remember that chief box down is then the only thing that stops the bill. --keep sets it for one run. |
box.maxHours | int | 0 (= 12) | When a box starts checking on its run, in hours after boot: from then on a run that has not committed in six hours is stopped and the box destroys itself, while one that is still committing goes on, up to four times this after it started. The backstop for the run that hangs rather than ends — a box nothing ever finishes on is a box nothing ever destroys. It never destroys a machine holding commits that exist nowhere else; it retries hourly instead. --max-hours sets it for one run. |
The review agent judges one story at a time, which leaves a blind spot no per-story check can cover. Because every story is built by a separate agent with a fresh context, two stories can each grow their own helper for the same job, or introduce competing patterns for one concern — and both commits still look correct in isolation. The damage only becomes visible when someone finally looks at the whole run.
The consolidation agent is that someone. It runs once, after the last story, and is the only agent that ever sees the entire run. What it looks for:
- parallel implementations of the same thing in different stories,
- competing patterns for one concern (two ways to handle errors, name things, access data),
- near-duplicate code that wants one abstraction,
- leftovers from abandoned approaches (dead code, unused helpers, debug output),
- drift from the codebase's own conventions.
Three properties keep it safe:
- Scoped to this run. The pass only ever sees
StartRef..HEAD— the commits this run landed. A followup run never reopens work an earlier run already reviewed, summarized or pushed. When the run landed no commits, or the window can't be determined, the pass is skipped rather than let loose on the whole branch. - Pure refactor, own commit. Behavior must not change, tests may not be bent to pass, and the work lands as one separate
refactor: consolidate <name> runcommit — never amended into a story commit — so it can be reviewed and reverted on its own. The prompt instructs the agent to run your project's checks and to revert rather than commit if it can't get them green. - Never blocks the run. Like the review, it's best-effort: a crash or an unfinished pass is surfaced as an event, but the stories stay done. It runs before the run summary and push, so the summary describes the consolidated result and the PR carries it.
It's on by default: a run nobody watches is exactly where two stories grow the same helper unnoticed. consolidate.enabled: false turns it off.
Trimming what a run is handed
An unattended iteration inherits the whole machine. In one measured project that meant 169 MCP tools from 25 servers — mail, a calendar, a task tracker, a hosting API — plus a catalogue of 59 skills, in a session started with --dangerously-skip-permissions. Across twenty runs the build agents called five of those tools, all from one server.
agent.mcp and agent.skills cut that down per project. Write the servers the work actually needs into a file and point the key at it:
// .chief/mcp.json
{
"mcpServers": {
"laravel-boost": { "command": "php", "args": ["artisan", "boost:mcp"] },
"notion": { "type": "http", "url": "https://mcp.notion.com/mcp" }
}
}agent:
mcp: .chief/mcp.json
skills: noneTwo things worth knowing:
- An account-level connector can be listed here. A remote server you connected through claude.ai — Notion in the example — reconnects from its URL with the authorization it already has, so naming it in the file keeps it while everything unnamed goes away.
- The startup context is what you are trimming, and you pay it per turn. It is not a one-off: every turn of an iteration re-reads the tool definitions and the skill catalogue. That is what makes a few thousand tokens at the start of a session worth removing at all.
Worktree command environment
worktree.setup and worktree.teardown run through sh -c with the worktree as their working directory. On top of the environment they inherit from Chief, both get:
| Variable | Value |
|---|---|
CHIEF_PRD_NAME | Name of the PRD the worktree belongs to, e.g. auth-rework — the directory name under .chief/prds/. |
CHIEF_BRANCH | Branch checked out in the worktree, e.g. chief/auth-rework. On a teardown that replaces a stale worktree, this is the branch that worktree is standing on, not the one about to replace it. |
CHIEF_BASE_BRANCH | Branch CHIEF_BRANCH was cut from, as recorded when Chief created it (see Pull request target). Empty when Chief has no record — for a branch it didn't create, it does not guess here. |
CHIEF_WORKTREE_PATH | Absolute path of the worktree. Same as the working directory, so a tool that changes directory itself can still find it. |
CHIEF_REPO_DIR | Absolute path of the main checkout — where the shared things live: an .env to copy, a seeded database to clone. |
Where the output goes
Both commands are watched while they run and recorded in full. The spinner shows the last few lines of the running setup — mixed stdout and stderr, in the order the script wrote them — so a multi-minute setup is something to watch rather than a frozen screen.
The complete output lands in the PRD directory, one file per run:
.chief/prds/<prd>/setup-2026-09-09-142530.log
.chief/prds/<prd>/teardown-2026-09-09-151204.logThat directory is the worktree's own — a run keeps the PRD's working files there, and the setup belongs to the worktree it prepares. A teardown whose worktree is about to be removed is the exception: its log would go with the directory, so it is written in the project instead.
Each file starts with the command that was run. The PRD directory's .gitignore already ignores *.log, so these stay out of version control alongside the run logs. When a command fails, the message in the TUI names its log file instead of dumping hundreds of lines of composer install into a modal.
This is what lets one script serve every worktree instead of parsing the directory it happens to sit in:
worktree:
setup: "./scripts/worktree.sh setup"
teardown: "./scripts/worktree.sh teardown"Both commands can also be run by hand against an existing worktree, with the same environment, the same working directory and the same log file — see chief worktree. That is how you re-run a setup after fixing the script, or retry a teardown that failed. chief worktree teardown does not remove the worktree.
#!/usr/bin/env bash
# scripts/worktree.sh — runs inside the worktree, one script for every PRD.
set -euo pipefail
# A slug both Postgres and Herd accept: auth-rework -> auth_rework
slug="${CHIEF_PRD_NAME//[^a-zA-Z0-9]/_}"
db="app_${slug}"
case "${1:?setup or teardown}" in
setup)
cp "$CHIEF_REPO_DIR/.env" .env
sed -i '' "s/^DB_DATABASE=.*/DB_DATABASE=$db/" .env
createdb "$db"
composer install && php artisan migrate
# Only diff against the base branch when Chief knows one. Written as an
# if rather than a `[ -n ... ] &&` one-liner: as the last statement under
# `set -e`, a false test would exit non-zero and fail the setup.
if [ -n "${CHIEF_BASE_BRANCH:-}" ]; then
git diff --name-only "$CHIEF_BASE_BRANCH"
fi
;;
teardown)
# Idempotent: it may run against a worktree that was already torn down.
dropdb --if-exists "$db"
;;
esacA CHIEF_* variable that happens to be set in Chief's own environment does not leak through — the values above always win.
Pull request target
A pull request has to merge back into the branch the work came from. In a repo where feature branches come off develop, a PR opened against main is the wrong merge — it drags every commit develop is ahead by into the diff, and merging it puts unreviewed work on the release branch.
So Chief doesn't assume the default branch. It decides the base in this order:
onComplete.prBaseBranch, when you set it. An explicit answer always wins.- The branch Chief cut the run's branch from. Chief records this in the repo's git config (
branch.<name>.chiefbase) at the moment it creates the branch, when the answer is still known for certain. Branches created from your current checkout record that branch; worktree branches are always cut from the default branch and record that. - The closest ancestor in history, for a branch Chief didn't create (or one created before this behavior existed). Chief asks every other branch how many commits the run's branch has added since they last shared a commit, and takes the branch that answers with the fewest — a branch cut from
developis a handful of commits pastdevelop, but those commits plus everythingdevelopgained since it leftmainpastmain. Ties go to the default branch, so two feature branches cut from the same point don't nominate each other. - The default branch, when there's nothing to go on.
A base that origin doesn't have is dropped rather than passed to gh, which would fail outright; gh then applies the repository default.
Existing pull requests. Before opening one, Chief checks whether the branch already has an open PR — the normal case for a followup run on the same branch. If it does, the push has already updated it, so Chief reports that PR (with its URL and target branch) instead of failing at gh pr create.
Example Configurations
Minimal (defaults):
worktree:
setup: ""
teardown: ""
baseBranch: ""
dir: ""
# setupOnReuse omitted: setup runs again on a reused worktree
onComplete:
push: false
createPR: falseFull automation:
worktree:
setup: "npm install && npm run build"
setupOnReuse: false
teardown: "./scripts/worktree.sh remove --self"
baseBranch: "develop"
dir: "../{repo}-worktrees/{prd}"
setupTimeoutSeconds: 900
onComplete:
push: true
createPR: trueSettings TUI
Press , from any view in the TUI to open the Settings overlay. This provides an interactive way to view and edit all config values.
Every key documented above has a row here — nothing is reachable only by hand-editing the YAML. Settings are organized by section:
- Worktree — Setup command (text), Run setup on reuse (three-way; Default = yes), Teardown command (text), Setup timeout in seconds (number; empty = no timeout), Base branch (text; empty = the detected default branch), Directory (text; empty =
.chief/worktrees/{prd}) - On Complete — Push to remote (toggle), Create pull request (toggle), PR base branch (text; empty = the branch the run's branch was cut from), Write run summary (toggle), Desktop notification (toggle)
- Loop — Keep machine awake (toggle), Watchdog timeout in seconds (number; empty = the built-in default)
- Agent — Provider (cycles through the supported CLIs; empty =
claude), CLI path (text), Model (text) - Review — Enabled (three-way), Model (text; empty =
sonnet), Skill (text), Instructions (text) - Consolidate — Enabled (three-way), Model (text; empty =
sonnet), Skill (text), Instructions (text)
The two Enabled switches and Run setup on reuse are three-way rather than on/off, matching the config: Enter cycles them Default → Yes → No → Default. On Default the value column shows what the setting currently resolves to — for the passes, Default (on) once a skill or instructions are set and Default (off) otherwise; for Run setup on reuse, always Default (on) — and no key is written to the file. Leaving a switch on Default is not the same as setting it to No, so editing an unrelated setting never freezes a derived pass at whatever it happened to resolve to at the time.
Changes are saved immediately to .chief/config.yaml on every edit.
When an edit takes effect
Most of these reach a run that is already in flight, so you can react to what you're watching instead of stopping the run first:
| Setting | Takes effect |
|---|---|
review.*, consolidate.* | The next review or consolidation pass. A review already under way finishes on the settings it started with, so switching the model mid-story doesn't restart anything. |
loop.watchdogTimeoutSeconds | The next agent invocation. The one currently running keeps the timeout it started with. |
loop.keepAwake | Within a few seconds, in either direction. |
onComplete.* | Read when the run finishes, so anything you change before then counts. |
worktree.setup | The next worktree created. Existing worktrees are not re-run. |
worktree.setupOnReuse | The next worktree start. A setup already running is not called off. |
worktree.teardown | The next worktree removal, so a command added mid-run still applies when you clean up afterwards. |
worktree.setupTimeoutSeconds | The next setup command. One already running keeps the timeout it started with. |
worktree.baseBranch | The next worktree created. A branch that already exists keeps the base it was cut from, which is also what its pull request targets. |
worktree.dir | The next worktree created, and the picker's next refresh. Worktrees already on disk stay where they are — move or clean them up yourself. |
agent.provider, agent.cliPath, agent.model | After restarting Chief. The agent CLI is resolved once at startup; the new value is written to the file but the running instance keeps the provider it launched with. |
When toggling "Create pull request" to Yes, Chief validates that the gh CLI is installed and authenticated. If validation fails, the toggle reverts and an error message is shown with installation instructions.
Navigate with j/k or arrow keys. The list scrolls when the terminal is too short for it; a ⋯ marks that it continues above or below. Press Enter to toggle booleans, cycle three-way switches and the provider, or edit text and number fields. Press Esc to close.
First-Time Setup
The settings a project needs once are asked by chief setup: the worktree setup command (proposed from the project's lockfiles), push and pull request at the end of a run (with a gh check), which MCP servers and skills an unattended agent gets, review and consolidation, and optionally the cloud box. Launching Chief for the first time in a project that has no .chief/config.yaml runs it automatically before asking for the first PRD's name.
Run chief setup again whenever you like: every question opens on the current value. Everything else can be changed in the Settings TUI (,).
CLI Flags
| Flag | Description | Default |
|---|---|---|
--agent <provider> | Agent CLI to use: claude, codex, opencode, cursor, or gemini | From config / env / claude |
--agent-path <path> | Custom path to the agent CLI binary | From config / env |
--model <model> | Model passed to the Claude CLI via --model | From config / env |
--max-iterations <n>, -n | Loop iteration limit | Dynamic |
--no-retry | Disable auto-retry on agent crashes | false |
--verbose | Show raw agent output in log | false |
Agent resolution order: --agent / --agent-path / --model → CHIEF_AGENT / CHIEF_AGENT_PATH / CHIEF_MODEL env vars → agent.provider / agent.cliPath / agent.model in .chief/config.yaml → default claude.
When --max-iterations is not specified, Chief calculates a dynamic limit based on the number of remaining stories plus a buffer. You can also adjust the limit at runtime with +/- in the TUI.
Agent
Chief can use Claude Code (default), Codex CLI, OpenCode CLI, Cursor CLI, or Gemini CLI as the agent. Choose via:
- Config:
agent.provider: opencodeand optionallyagent.cliPath: /path/to/opencodein.chief/config.yaml - Environment:
CHIEF_AGENT=opencode,CHIEF_AGENT_PATH=/path/to/opencode - CLI:
chief --agent opencode --agent-path /path/to/opencode
Agent-Specific Configuration
Each agent has its own configuration. For example, when using Claude Code:
# Authenticate: run claude and follow the browser prompts
claude
# Pick a model interactively with /model inside the session,
# or let Chief pass one through with --model / agent.modelSee the Claude Code documentation for details.
When using Cursor CLI:
# Authentication (or set CURSOR_API_KEY for headless)
agent loginChief runs Cursor in headless mode with --trust and --force so it can modify files without prompts. See Cursor CLI documentation for details.
Appearance
The TUI adapts to your terminal automatically and can be forced into plainer modes via environment variables.
- Adaptive colors — the color palette detects your terminal's background and picks a matching variant: the original bright theme on dark terminals, and darker, higher-contrast tones on light terminals (the dark-mode pale greens/yellows are nearly invisible on white). No configuration required.
NO_COLOR— set to any non-empty value (per no-color.org) to strip all colors and styling. The TUI degrades to plain text.CHIEF_ASCII— set to a truthy value (1,true,yes,on) to replace Unicode/emoji icons with ASCII fallbacks. Useful for terminals, multiplexers, or piped logs that render emoji poorly (mis-measured widths break the layout).
| Variable | Effect |
|---|---|
NO_COLOR | Any non-empty value disables all colors/styling (plain text). |
CHIEF_ASCII | Truthy value (1/true/yes/on) switches status icons, log tool cards, and decorative glyphs to ASCII. |
Icon fallbacks in ASCII mode:
| Unicode | ASCII | Meaning |
|---|---|---|
✓ | v | Passed |
● | * | In progress |
○ | . | Pending |
✗ | x | Failed |
◆ | ! | Needs review |
📖 ✏️ 📝 🔨 🔍 🔎 🤖 🧐 🌐 ⚙️ | [R] [E] [W] [$] [G] [/] [T] [S] [@] [*] | Log tool cards (Read/Edit/Write/Bash/Glob/Grep/Task/Skill/Web/other) |
Permission Handling
Some agents (like Claude Code) ask for permission before executing bash commands, writing files, and making network requests. Chief automatically configures the agent for autonomous operation by disabling these prompts.
WARNING
Chief runs the agent with full permissions to modify your codebase. Only run Chief on PRDs you trust.
For additional isolation, consider using Claude Code's sandbox mode or running Chief in a Docker container.