Skip to content

fix(codex): pre-trust a worker's working directory so it can boot unattended - #21

Merged
aaroncoville merged 2 commits into
mainfrom
upstream/codex-directory-trust
Aug 26, 2026
Merged

aaroncoville merged 2 commits into
mainfrom
upstream/codex-directory-trust

Conversation

@aaroncoville

@aaroncoville aaroncoville commented Aug 26, 2026 •

Copy link
Copy Markdown
Owner

What & why

A Codex agent spawned with nobody at the keyboard would sit at its very first turn and never
start. Codex gates every working directory behind an interactive "do you trust this directory?"
prompt, and the answer is persisted per config home as a [projects."<dir>"] trust_level = "trusted" table. Each agent is given its own isolated CODEX_HOME so the user's ~/.codex is
never mutated — and that home is generated fresh, so it has never answered the prompt for anything.
Every such agent therefore met the prompt on turn one and stalled there indefinitely.

Pre-seeding that table is the only suppression that exists. No flag or environment variable skips
the prompt (notably not --dangerously-bypass-approvals-and-sandbox, which governs command
approval, not directory trust), and a -c projects… override is not persisted, so it does not read
as an answer either. Verified against codex-cli 0.149.1.

So the fix appends the trust entry to the generated config.toml before the CLI starts, keyed on
the final working directory (git isolation has already resolved by the time spawn prep runs, so
this is the agent's own worktree when one was created and the original repo when isolation fell
back) in its canonical, realpath-resolved spelling (Codex resolves the directory before looking
it up, so an entry keyed /tmp/x never matches its /private/tmp/x and the prompt returns). The
path goes through a TOML basic-string escaper rather than string interpolation: a directory name may
legally contain a quote or a backslash on POSIX, and an unparseable config.toml would be a worse
failure than the prompt it was meant to remove.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Docs
  • Build / CI

Evidence

The failure is a CLI process that hangs rather than anything visual, so the evidence is the
generated config.toml and the boot behaviour, captured as text.

Before

CleanShot 2026-08-26 at 14 45 52@2x

After

CleanShot 2026-08-26 at 14 49 13@2x

How I tested it

  • OS: macOS 15 (Darwin 25.6.0), Node via the repo toolchain; codex-cli 0.149.1.
  • Steps:
    1. Measured the baseline on the branch point first: npm run test:focused on main →
      552 of 552 passing, npm run typecheck → 0 errors.
    2. On this branch: npm run typecheck → 0 errors.
    3. npm run test:focused → 556 of 556 passing, 0 failing (the 4 new tests are the delta;
      no pre-existing failures on either ref).
    4. npm run build → succeeds.
    5. Confirmed the tests are load-bearing by reverting only src/main/hive.ts to main while
      keeping the new tests: all 4 go red (transcript under Before). Restored, green again.
    6. Checked the escaping and canonicalisation cases explicitly — a directory containing a quote
      round-trips through the TOML escaper, and the entry is keyed on the realpath, which is what
      makes it match on macOS where /tmp resolves to /private/tmp.

Discord (optional)

Discord:

Checklist

  • Before and after evidence is attached above, under both headings.
  • npm run typecheck passes.
  • npm run test:focused passes.
  • npm run build succeeds.
  • This PR is one change. Unrelated fixes belong in their own PR.
  • I read the diff myself before opening this, and there is no debug output,
    commented-out code, or unrelated formatting churn in it.
  • Any new UI derives from DESIGN.md / tokens.ts — no ad-hoc colors,
    spacing, or fonts. (No UI in this change.)
  • If I added art, it's my own or compatibly licensed, and listed in
    ATTRIBUTION.md. (No art in this change.)

github-actions Bot and others added 2 commits August 26, 2026 19:03
Co-authored-by: wall-sync <wall-sync@users.noreply.github.com>
…ttended

A Codex worker spawned without a human at the keyboard would sit at its very
first turn and never start.

Codex gates every working directory behind an interactive "do you trust this
directory?" prompt, and the answer is persisted per config home as a
`[projects."<dir>"] trust_level = "trusted"` table. Each worker gets its OWN
isolated CODEX_HOME (so the user's ~/.codex is never mutated), and that home is
generated fresh: it has never answered the prompt for anything. So every worker
met the prompt on turn one, and an unattended one had nobody to press Yes.

Pre-seeding that table is the only suppression that exists. No flag or
environment variable skips the prompt — `--dangerously-bypass-approvals-and-
sandbox` governs command approval, not directory trust — and a `-c projects…`
override is not persisted, so it does not read as an answer either.

So append the trust entry to the generated config.toml before the CLI starts,
seeded from the user's config as before, keyed on:

- the FINAL working directory. Git isolation has already resolved by the time
  spawn prep runs, so this is the worker's own worktree when one was created
  and the original repo when isolation fell back — always the directory the CLI
  will actually open in, and one entry is enough.
- its canonical, realpath-resolved spelling. Codex resolves the directory
  before looking it up, so an entry keyed `/tmp/x` never matches its
  `/private/tmp/x` and the prompt comes back.

The path goes through a TOML basic-string escaper rather than string
interpolation: a directory name may legally contain a quote or a backslash on
POSIX, and an unparseable config.toml would be a worse failure than the prompt.

The short symlinked home that remote control binds its control socket through
needs nothing extra — it aliases the same directory, so it sees the same file.
@aaroncoville

Copy link
Copy Markdown
Owner Author

Note for review — one file in this diff is not part of the change.

docs/wall-data.json appears here only because this fork's main is one upstream commit behind: it is missing b9f34e71 ("wall: 5 new Founding Supporter(s) (HarnessMD#319)"), which touches that file. This branch was cut fresh from origin/main (upstream tip), so GitHub shows that upstream commit as part of the diff against the fork's base.

Against the real target it is clean:

$ git log origin/main..HEAD --oneline
b863af8f fix(codex): pre-trust a worker's working directory so it can boot unattended

$ git diff origin/main...HEAD --stat
 src/main/hive.ts                    | 24 +++++++--
 src/shared/codexTrust.ts            | 42 ++++++++++++++++
 test/codex-directory-trust.test.cjs | 97 +++++++++++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+), 3 deletions(-)

Exactly one commit, only the intended files, no fork-only files. The PR body above is written to the upstream template and can be reused verbatim when the upstream PR is opened — the only thing it still needs is the terminal captures under ### Before / ### After; the repro steps and argv are already there as text.

@github-actions

github-actions Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

✅ Evidence received. Before and after are both attached. Thanks — this is what makes a PR reviewable in one pass.

@aaroncoville
aaroncoville merged commit 8d5ab2a into main Aug 26, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant