Development record
2026-10-16 — A headless claude run whose role's contract grants the shell gets its permission prompts answered by abcd's guard through the harness's --permission-prompt-tool, naming the one tool of an 'abcd guard approve' MCP server that the launch passes inline with --mcp-config and --strict-mcp-config, under --permission-mode default; the shell is no longer pre-granted on that launch, so every shell command the harness would ask about reaches the guard, which approves only a clean allow (no entry, signal or diagnostic, a program the line names, no inline code handed to a non-shell interpreter) and refuses every other prompt with the guard's reason. The expected shape, a PreToolUse hook in a per-launch --settings file, cannot work: --bare skips every hook, those --settings declares included (probed on claude 2.1.301: a SessionStart and a UserPromptSubmit hook from --settings ran without --bare and not with it), and dropping --bare would let the repository's hooks, plugins and CLAUDE.md run. A role whose contract grants no shell keeps dontAsk, which already refuses every prompt; opencode has no per-launch approval callback and is unchanged; the person's host settings are never touched. This change adds no sandbox; sandboxing autonomous runs is a separate later step (dcn-2610161619296615). Grounds: claude --help 2.1.301; code.claude.com/docs/en/cli-reference, /headless, /agent-sdk/permissions, /agent-sdk/user-input; live probes of the prompt tool's request and allow/deny answers, and of --bare connecting an --mcp-config server.
2026-10-16 — A headless claude run whose role's contract grants the shell gets its permission prompts answered by abcd's guard through the harness's --permission-prompt-tool, naming the one tool of an 'abcd guard approve' MCP server that the launch passes inline with --mcp-config and --strict-mcp-config, under --permission-mode default; the shell is no longer pre-granted on that launch, so every shell command the harness would ask about reaches the guard, which approves only a clean allow (no entry, signal or diagnostic, a program the line names, no inline code handed to a non-shell interpreter) and refuses every other prompt with the guard's reason. The expected shape, a PreToolUse hook in a per-launch --settings file, cannot work: --bare skips every hook, those --settings declares included (probed on claude 2.1.301: a SessionStart and a UserPromptSubmit hook from --settings ran without --bare and not with it), and dropping --bare would let the repository's hooks, plugins and CLAUDE.md run. A role whose contract grants no shell keeps dontAsk, which already refuses every prompt; opencode has no per-launch approval callback and is unchanged; the person's host settings are never touched. This change adds no sandbox; sandboxing autonomous runs is a separate later step (dcn-2610161619296615). Grounds: claude --help 2.1.301; code.claude.com/docs/en/cli-reference, /headless, /agent-sdk/permissions, /agent-sdk/user-input; live probes of the prompt tool's request and allow/deny answers, and of --bare connecting an --mcp-config server.
Frontmatter
| id | dcn-2610161624025493 |
|---|---|
| type | decision |
| date | 2026-10-16 |
.abcd/work/decisions/dcn-2610161624025493-a-headless-claude-run-whose-role-s.md
open on GitHub ↗ · commit history ↗
mentions6
- dcn-26101616192966152026-10-16 — Correction to dcn-2610161612269647: sandboxing autonomous runs was not declined. The 2026-10-16 question offered only teaching and per-launch approvals, after the sandbox option was dropped to fit the screen, so the product thinker made no ruling on it; it stays open, and the product thinker named it on the same day as an important feature for autonomous runs.
- dcn-26101618214200882026-10-16 — Refines dcn-2610161624025493: the headless approver's own checks read the guard's parse of the line, not its raw text. Writes and removals are the targets the guard's write model resolves (writesOf), in every shell state the reading placed each segment in, plus the operands of touch, mkdir, chmod, chown, truncate, rm, rmdir, unlink, mv's sources, ln's link targets and a sed script's w commands; a target the line does not fix (a variable, a substitution's output, a glob, or a relative name after an unknown change of directory) is refused, and a fixed one is compared, with its existing directories resolved through symlinks and its case folded where fsutil.CaseFoldingFS says the filesystem folds, against the protected set: the git common directory with its hooks and config, each core.hooksPath directory, each checkout's .git, the home shell start-up files and git configuration, the host's settings directories, .mcp.json and .abcd/guard.json, plus the harness's protected names as path components relative to the working tree or home. A line that only names a protected path is no longer refused. git config is refused with --global, --system or --file on a write, on setting a key that starts a program (the gitutil.ExecPins keys and git's documented program settings) and on opening the editor, and so is any git command setting such a key for itself. The approver takes --base, the lane's base or the worktree's HEAD at an interview's launch, and the working tree's registry is in force only where it is at least as strong as the one at that commit: any weakening, of a warn or a blocker, committed during the run or not, refuses every command, and a shell launch with no base is not started. An allow returns only the command and its description, and a call asking to run outside the host's sandbox is refused. Critical removals widen to home's direct children and the repository's other checkouts, and to a find that deletes from home or above, a move of a critical directory, an rsync --delete into one and a recursive chmod or chown of one. Code reaching an interpreter outside -c/-e is refused: on the standard input of a listed interpreter or ex that names no script file, an awk program that runs a command, a sed script with the e command or flag, tar's options that run a program, and a vim or ex command holding a !. A glob in a removal target is refused as not fixed except where it lists a critical directory, which the critical check refuses first. The checks read the line itself; a script the line runs is read for the registry's hazards, not for its own writes. The guard stays a mistake filter (adr-42) and this adds no sandbox (dcn-2610161619296615).
- iss-2610240932215918The claude runner cannot run on a Claude subscription, so a person without an API key has no route to any role abcd runs from a plain terminal: the planning interview, a build's implementer and validators, the drain's judge.
- itd-59Every Autonomous Run Pass Leaves the Same Durable, Queryable Transcript an Interactive Session Does
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is open
- itd-2610240933025076abcd plans unattended and asks only what a person owns