Configuration
The complete devplane.toml reference. Every key is read by the code; an unknown key is a parse error rather than a silent default.
devplane.toml sits at the repository root and is committed. Without one there are no gates and no rules. Devplane never writes it, except [spec] open_questions when you accept that offer.
An unknown key is a parse error. A file that will not load fails closed: every gated call in that repository goes to a person.
devplane config check [path] says what the file will do and exits non-zero on an error. It runs offline, so CI can run it.
The complete file
[project]
name = "saas"
base_branch = "main" # discovered from the repository when unset
[agent]
default = "claude" # used by `change start` without --agent
max_parallel = 2 # changes with an agent in them at once
stall_timeout = "12m"
keep_transcripts = true
[workspace]
setup = "pnpm install --frozen-lockfile" # run once in a new worktree
share = ["cargo"] # build caches shared by changes
[gates]
check = ["pnpm typecheck", "pnpm lint", "pnpm test -- --run"]
timeout = "10m" # for the whole gate
max_feedback_rounds = 2 # then the change stops for a person
junit = ["reports/junit.xml"] # JUnit files check writes; names tests
base_checks = "offer" # offer | auto | off
serial = false # true: one gate at a time (a shared database)
[gates.e2e] # run by name; never what verifies a change
run = ["pnpm test -- --run tests/e2e"]
timeout = "20m"
[policy] # refuse and defer; Devplane never approves
deny = ["Bash(rm *)", "Read(.env)"]
ask = ["Bash(git push *)"]
[budget] # per change
max_usd = 10
max_turns = 60
max_runtime = "45m"
[spec]
plans = "specs"
open_questions = ["NEEDS CLARIFICATION", "TBD"]
tokens = ["REQ-", "US-"]
[questions]
deadline = "never" # never | 90s | 30m | 4h
hold = "30s" # up to 120s
[review.roles] # first matching role wins
shared = ["src/types/**", "migrations/**"]
security = ["src/auth/**"]
tests = ["tests/**", "*.md"]
[[review.covers]]
test = "tests/auth.rs"
paths = ["src/auth/**"]
[github]
pull_request = true # lets `change offer` push and open the PR, as a draft
ready_label = "devplane:ready"Durations are strings: "30s", "10m", "1h30m"; "600" is seconds. A bare number (timeout = 600) is a parse error. [questions] deadline takes one unit ("90s", "30m", "4h") or "never".
[project]
| Key | Type | Default | Meaning |
|---|---|---|---|
name | string | the directory name | what the workbench calls it |
base_branch | string | discovered from the repository | what worktrees branch from; must be a name git accepts |
[agent]
| Key | Type | Default | Meaning |
|---|---|---|---|
default | string | claude | the agent devplane change start uses when --agent is not given |
max_parallel | integer | unlimited | changes with an agent in them at once in this project; sessions you started yourself are not counted |
stall_timeout | duration | 10m | how long a working run may produce nothing before it is called stalled |
keep_transcripts | bool | true | whether what a driven agent says in this repository is stored |
keep_transcripts = false stores nothing a driven agent says, prompts included. Watched sessions carry no prose either way.
[workspace]
| Key | Type | Default | Meaning |
|---|---|---|---|
setup | string | none | a command run once in a new worktree, before the agent starts |
share | list of ecosystem names | empty | build caches shared between this project’s changes |
setupis not a gate. While it runs, the change reads installing ·<command>·. shareis never inferred, and an unknown name is refused. The one name iscargo: setup, gates and agent getCARGO_TARGET_DIRunder.claude/worktrees/.shared/. pnpm, npm, go and uv share a home store without it.- Files to copy into a fresh tree (
.env, local config) go in.worktreeinclude(gitignore syntax, also read by Claude Code). Only gitignored files are copied, symlinks out are refused, and nothing at the destination is overwritten.[gates]Key Type Default Meaning checklist of commands empty the definition of done; all must succeed, in order timeoutduration 10mfor the whole gate max_feedback_roundsinteger 2how many times failures go back to the agent before the change stops for a person; 0stops at oncejunitlist of paths empty JUnit XML files checkwrites, relative to the checkout; read only when the run wrote them, inside the checkout, up to 16 MiB, to name tests. Git should ignore them: writing a tracked path moves the treecheckis judged onbase_checksoffer·auto·offofferwhen base checks run: when you offer or ask, also after every passing check, or neverserialbool falserun this repository’s gates one at a time on this machine ( setup,check, named gates, base checks,devplane gate), for checks that share a test database or a fixed port. Thetimeoutstarts when a gate’s commands do. Not serialised across machines[gates.<name>]Any other table under
[gates]is a named gate. The keys above are reserved and cannot name one; any other key that is not a table is refused.Key Type Default Meaning runlist of commands required the commands; an empty list is an error timeoutduration the [gates]timeoutoptional override A named gate runs on request (
devplane gate --name e2e) and never verifies a change. See Verified done.[policy]Key Type Default Meaning denylist of rules empty refused, without asking anybody asklist of rules empty put in front of a person Neither grants anything. Syntax: Permissions.
[budget]Per change. Passing a bound stops the change with a
cost_spikeitem naming it. Unset means no bound;max_usd = 0also means no ceiling.Key Type Meaning Always fires? max_usdnumber what one change may spend, as its agent reports it no max_turnsinteger agent turns one change may take yes max_runtimeduration how long one change may run, from when it started yes max_usdbinds only agents that report cost (optional in ACP).config checkwarns when it is the only bound;change showsays not reported rather than$0.00.[spec]Key Type Default Meaning planspath none where this repository keeps its specifications ( specs, Kiro’s.kiro/specs, OpenSpec’sopenspec/changes)open_questionslist of strings empty words that mark a question a specification has not answered tokenslist of prefixes FR-NFR-SC-REQ-US-AC-the shapes of requirement ids; a prefix followed by digits plansunset: the Specifications view says not configured.open_questionsmatches per line, case-insensitively, outside code; one inbox item per project names the count.tokensreplaces the defaults and is case-sensitive.REQ-3in a heading and a task line links them. No digits in a prefix; for bare numbers write the word (tokens = ["Req "]matchesReq 12). An empty list is an error.
See Specifications.
[questions]Key Type Default Meaning deadlineneveror a durationneverhow long an unanswered question or permission waits before the agent is told no holdtrue,falseor a durationnone how long a permission on a watched session waits for an answer from Devplane before the agent’s own dialog appears. trueis 30s; a duration is at most 120s- Without
deadline, only a person ends a question. A fireddeadlinetells the agent no, recorded astimerwith the duration and file. The clock runs only while a host is up. An unparsable value failsconfig check, and the wait stays unbounded. holdapplies only toaskmatches, never todeny, and not in auto mode. See answering a watched session’s permission.
[review]How
devplane change reviewand the Review tab order files after the weakened checks. Nothing is inferred from file names.Key Type Default Meaning roles.sharedpatterns empty shared types, schemas, migrations — read first roles.logicpatterns empty business logic roles.securitypatterns empty security boundaries roles.integrationpatterns empty integration points roles.wiringpatterns empty routes, commands, glue roles.testspatterns empty tests and docs — read last covers.teststring — a test, as you name it; a gate command containing it is shown as the command that runs it covers.pathspatterns — what that test covers; must not be empty [review.roles] shared = ["src/schema.sql"] logic = ["src/core/**", "!src/core/auth.rs"] security = ["src/core/auth.rs"] integration = ["src/clients/**"] wiring = ["src/cli/**"] tests = ["tests/**"] [[review.covers]] test = "tests/auth.rs" paths = ["src/core/auth.rs"]- Patterns are gitignore syntax, repository-relative. First matching role wins; unmatched files follow in path order.
- Without roles the review says it is unordered. Without
[[review.covers]]the coverage column is absent (not not covered).
[github]Key Type Default Meaning pull_requestbool falselet devplane change offerpush and open a draft pull request; off, it prints thegit pushline and compare address. Passing gates never opens oneready_labelstring none issues with this label are offered as work — devplane issue list --ready,change start --issueMachine-wide files
All under
~/.devplane/(orDEVPLANE_HOME).policy.tomlholds your machine-wide rules and your grants. It fails closed.[policy]withdenyandask, applied to every project.allowis refused.[grants]withoffer_afterandoffer_again_after(default 5, at least 2): when a grant is offered.[projects."<root>"]withallow, and its ownoffer_afterandoffer_again_after.
A repository’s
devplane.tomlwith[policy] allow,[grants]or[projects]is refused. See grants.app.tomlholds the window’s settings and the GitHub this machine signs in to:# ~/.devplane/app.toml [app] shortcut = "CmdOrCtrl+Shift+Space" # the one global shortcut port = 47831 # the host's port; 0 picks a free one updates = "off" # "on" | "off"; absent, the app never checks [github] host = "ghe.corp" # the configured host; github.com unless set client_id = "Iv1.…" # that host's OAuth App, for the device flow [github.hosts."ghe.other"] client_id = "Iv1.…" # another Enterprise server's own app- A client id is public. Release builds carry one for
github.comonly; other hosts usedevplane auth login --with-token. updatesis read only by the window’s process; the command line never checks for updates.- A missing file means defaults.
devplane doctorreports a parse error or an unregistrable shortcut. port = 0picks a new port each start, so agent telemetry (cost, context) does not arrive.
agents.tomladds agents by name, or sets a built-in’smode. Keys per[agents.<id>]:name,command(optional for a built-in id) andmode. See Driving agents.trusted.tomllists the repositories you trusted, written bydevplane project trustand the window. Its one key,roots, is a list of absolute paths; any other key is refused and trusts nothing. Delete a root to take its trust back; the host reads the file when it starts. The store never holds trust.# ~/.devplane/trusted.toml roots = ["/Users/you/src/api"]Environment
Variable What DEVPLANE_HOMEwhere the store, token and host record live (default ~/.devplane)DEVPLANE_PORTthe port devplane upbinds (default47831)DEVPLANE_LOGthe host’s log filter, e.g. devplane=debugDEVPLANE_NOTIFY0turns desktop notifications offDEVPLANE_CLAUDE_BINthe claudebinary, when it is not onPATHDEVPLANE_UIserve the interface from this built directory instead of the embedded copy DEVPLANE_OPENCODE_URLa running opencode serveto watch; see Watching sessionsVISUAL,EDITORthe editor the window’s Editor button opens a worktree in DEVPLANE_RUNset by Devplane on every agent it starts, so devplane inbox answer --allowrefuses inside itCLAUDE_CONFIG_DIRwhich Claude Code configuration devplane agent connect claudewrites toCODEX_HOME,COPILOT_HOMEwhere Codex and Copilot keep their configuration NO_COLOR,CLICOLOR_FORCEcolour off, or forced on Something wrong or out of date? Edit this page.