Repository navigation
fix(bin): pre-register Codex workspace trust before spawn - #6731
andrewkimcanidium wants to merge 8 commits into
Conversation
|
|
Speaking as Kun's firstmate: thanks, @andrewkimcanidium. I read the whole diff of head What I checked: the new CI: I approved this head's fork runs Contract-class: new-default. On main, an unconfigured Codex spawn launches without touching the user's Codex config. With this PR, every Codex worker and secondmate spawn writes a persistent trust entry into the user-global Things Kun will weigh (no action needed unless you want to address them): workspace trust is a security-relevant Codex setting, and this grants it automatically on every spawn. Entries also build up in VISION.md, rule by rule
Next step: this is waiting on CI. Once CI and Require no-mistakes are green on |
2d26479 to
c902397
Compare
| cat > "$script" <<SH | ||
| #!/bin/sh | ||
| if [ "\${1:-}" = app-server ]; then | ||
| FM_TEST_CODEX_STORE='$CODEX_HOME/config.toml' exec '$(command -v node)' '$ROOT/tests/codex-config-fixture.js' |
There was a problem hiding this comment.
Tests touch personal Codex settings
fm_test_remote_codex_config redirects only the fixture's file writes. The remote job clears CODEX_HOME and uses the real account's HOME, so fm-codex-trust.sh still creates ~/.codex and checks the operator's real config.toml before starting the fixture. These portable tests leave a directory outside their temporary folders. If the operator's config is symlinked, the tests can also fail for reasons unrelated to the behavior being tested.
Give the remote trust helper a temporary CODEX_HOME, not just the fixture a different output file.
|
Speaking as Kun's firstmate: thanks for the follow-up, @andrewkimcanidium. I re-read the thread and the new head What changed: CI: the previous head's CI run ( One non-blocking note (Greptile P2, "Tests touch personal Codex settings", Contract-class: new-default (same as last pass). On main, an unconfigured Codex spawn launches without touching the user's Codex config. With this PR, every Codex worker and secondmate spawn writes a persistent Security FYI (for Kun, no action needed from you): this grants a security-relevant Codex setting automatically on every spawn. It's tightly scoped to the exact launch directory and fails closed, but the entries accumulate in VISION.md, rule by rule
Overlap note for Kun: #6397 ("prevent Codex and Pi folder-trust stalls in pooled worktrees"), #5067 ("register Codex directory trust during authorized intake") and #4432 ("handle fresh workspace trust at startup") go after the same Codex trust prompt. #6763 also edits the Codex launch in Next step: waiting on CI for |
1383563 to
b418873
Compare
|
Speaking as Kun's firstmate: thanks for the new round, @andrewkimcanidium. I re-read the thread and the new head What changed since CI: Require no-mistakes is green on Contract-class: new-default (unchanged). On main, an unconfigured Codex spawn doesn't touch the user's Codex config. With this PR, every Codex worker and secondmate spawn writes a persistent Security FYI (for Kun, no action needed from you): this automatically grants a security-relevant Codex setting on every spawn. It's scoped to the exact launch directory and fails closed. The entries do accumulate with no teardown cleanup. VISION.md, rule by rule
Overlap note for Kun (unchanged): #6397, #5067 and #4432 target the same Codex trust prompt. Next step: this is waiting on CI for |
…rust helper could hang if the Codex server stalled while shutting down. The rule that was broken: once the helper has a result (trusted or refused), it must exit within a fixed time, whatever the Codex server does. Both the success path and the refusal path end in one shared `finally` cleanup in `bin/fm-codex-trust.sh`. That cleanup only closed stdin and sent SIGTERM, so a server that ignored SIGTERM kept its process and stdout open. The helper then never exited, and `fm-spawn.sh` waited forever instead of reporting the refusal. **Fix** (in that one cleanup, which covers both paths): after SIGTERM, a 2-second timer sends SIGKILL and closes the server's stdout. The timer is unref'd, so a server that exits normally adds no delay. I updated the help header to mention the 2-second grace and extended the `--help` sed range by one line to match. **Regression test:** - `tests/codex-config-fixture.js` has a new `FM_TEST_CODEX_CONFIG_STALL` mode. The fixture records its pid, ignores SIGTERM and stays alive. - `tests/fm-codex-trust.test.sh` runs a refusal (broken config) against that stalling fixture. It checks that the helper exits within 10 seconds with code 1 and that the stalled server is no longer running. - With the old cleanup the new test fails ("helper hung on a server that stalled during shutdown"); with the fix it passes. **Verification:** - The full `tests/fm-codex-trust.test.sh` passes, about 11 seconds in total. - `fm-spawn-worktree-settle`, `fm-spawn-orca-worktree` and `fm-control-relaunch` pass. - `shellcheck` is clean on both changed shell files. I did not push or re-run validation; the outer executor owns those phases
…tructure problems. I made a fix, but I had not confirmed that the failing suites now pass when this result was required. **Cause.** `bin/fm-spawn.sh` now runs `bin/fm-codex-trust.sh` before every codex spawn, and that helper starts `codex app-server`. CI runners have no `codex` binary. So every suite that spawns a codex worker or secondmate without the fixture got `refusing to pre-register Codex workspace trust: spawn codex ENOENT` and refused to launch. The CI logs show this in fm-secondmate-lifecycle-e2e, fm-control-herdr-smoke, fm-remote-secondmate-parent-binding, fm-remote-secondmate-lifecycle-e2e and fm-spawn-compact-adviser-disable. The other failing suites in those shards are fm-secondmate-harness, fm-secondmate-safety, fm-secondmate-restart, fm-wake-queue, fm-remote-secondmate-trace-context and fm-spawn-compact-adviser-disable-remote; their logs don't show the error text, but they also spawn codex. The same gap leaks on a developer machine that has codex: running the suite locally had written a `/tmp` trust entry into the real `~/.codex/config.toml`. **Rule that was broken.** No spawn under test should need an installed codex or write the operator's real Codex store. The previous rounds fixed only four suites one by one, so I fixed it in `tests/lib.sh`, which every suite loads: - I moved `fm_test_fake_codex_config` from `tests/fixtures.sh` into `tests/lib.sh` without changing it. `fixtures.sh` loads `lib.sh`, so existing callers still find it. - When `lib.sh` loads, it creates a temporary directory per suite and exports `CODEX_HOME` pointing into it. It also puts the config-only `codex` wrapper first on PATH. That wrapper answers `codex app-server` with `tests/codex-config-fixture.js` and passes any other command to the real codex if one exists, otherwise exits 0. - `fm_live_gate`, just before its tool checks, removes that wrapper from PATH and restores the operator's original `CODEX_HOME` (or unsets it). Live guards such as fm-codex-workspace-trust-live-e2e therefore still use the real codex, and in CI a live codex guard still skips because codex is absent. Production code is unchanged. **Verification.** - `shellcheck -x tests/lib.sh tests/fixtures.sh` is clean. - I built a PATH without codex to imitate CI. With the unfixed test library, fm-secondmate-lifecycle-e2e reproduces the exact CI error (`spawn codex ENOENT` and "secondmate spawn failed"). - I had not confirmed any pass with the fix. Two runs were still going when this result was due: - The failing suites: fm-secondmate-lifecycle-e2e, fm-secondmate-harness, fm-secondmate-safety, fm-spawn-compact-adviser-disable, fm-secondmate-restart, fm-remote-secondmate-lifecycle-e2e, fm-remote-secondmate-trace-context, fm-spawn-compact-adviser-disable-remote, fm-codex-trust and fm-control-herdr-smoke. - Suites the new setup could affect: fm-live-gate, fm-test-fixture-cleanup, fm-remote-doctor, fm-bootstrap, fm-spawn-orca-worktree, fm-spawn-worktree-settle and fm-control-relaunch. - An earlier batch on a broken simulated PATH (node missing) can't be used as evidence. On that batch, fm-remote-secondmate-parent-binding passed. - I have not run fm-wake-queue with the fix. **Risk.** Non-live suites now always find a `codex` on PATH, even in CI. A suite that checks behaviour when codex is not installed (for example around fm-remote-doctor) could behave differently; fm-remote-doctor is in the pending run, and the rest of the CI matrix still has to confirm. "Behavior portable serial 2" was cancelled, not failed, and is not part of this round's findings
…R. The failing suite was tests/fm-secondmate-harness.test.sh, with "not ok - split: no meta written". I fixed it in that test file and it now passes locally. Production code is unchanged. **Cause.** Every codex spawn now runs `bin/fm-codex-trust.sh`, which calls `codex app-server`. `tests/lib.sh` puts a config-only codex wrapper first on PATH for this. But this suite runs its spawns with a minimal PATH (fakebin, BLIND_BIN, then `/usr/bin:/bin:/usr/sbin:/sbin`), so the wrapper drops out. The codex secondmate spawn then can't find codex, refuses to launch and writes no meta. I reproduced the same failure locally, since that minimal PATH has no codex on macOS either. **Rule that was broken.** Every spawn under test must find the config-only codex fixture on its PATH. In this suite, the shared place to fix that is the two spawn fakebin builders: `make_noop_tmux` (used by `spawn_secondmate` and the unverified-harness case) and `make_launch_capturing_tmux` (used by `spawn_secondmate_capture`, the cursor case and the crew case). Both already link node in for the Claude trust registration. **Fix.** Each builder now copies `$FM_TEST_CODEX_ROOT/bin/codex` into its fakebin, next to the node link. It is a copy, not a symlink. My first try used a symlink, and it broke a later test: case C9 writes its own probe to `$fakebin/codex` with `cat >`, which wrote through the symlink and overwrote the shared wrapper. That made the later "fallback ... crew-harness codex" check fail. The other fakebin builder, `make_fake_toolchain`, is used by non-spawn cases, which never fail on a missing codex. **Verification.** - Before the fix, `bash tests/fm-secondmate-harness.test.sh` reproduced the CI failure. - After the fix it exits 0 with all 51 checks passing. - `shellcheck -x` on the suite is clean. - The other 27 suites in shard 8 already passed in CI. I did not re-run them
b418873 to
3ffebb8
Compare
|
Speaking as Kun's firstmate: thanks for round 6, @andrewkimcanidium. I re-read head Delta since Live evidence: 3 of 5 scenarios ran live against real codex-cli 0.159.3: no trust prompt in a fresh worktree, a 138-registration race with 0 lost, and the operator store left byte-identical. The refusal paths and stalled-shutdown kill are fixture-only, which is reasonable. Contract-class: new-default (unchanged). Every unconfigured Codex spawn now writes a persistent VISION: One captain: aligns. Authority: cannot tell (default-on trust grant needs Kun's word). Scripts/mechanics: aligns. Restart: aligns. Delegation: aligns (refuses instead of guessing). Vendor: aligns (vendor config API). Scope: aligns. Next: this is waiting on CI. Once CI and Require no-mistakes are green, it goes to Kun for a merge-or-leave decision. That part is on him, not you. Overlap note for Kun: #6397, #5067, #4432. |
Intent
Okay, what can we dispatch? (Captain chose 2026-10-06: CPQ research pack, Drive how-to inventory, fleet fixes, usage-axi source registry.)
Backlog note:
"Firstmate pre-registers workspace trust for Claude workers before launch, and does not do the equivalent for Codex. The asymmetry is visible in two ways.
A Claude spawn that cannot pre-register refuses outright rather than launching: "could not pre-register Claude workspace trust for ; refusing to launch a claude worker that would wedge on the trust dialog". That is the guard working, and it is good behaviour.
A Codex spawn has no such step, so every worker launched into a fresh treehouse worktree stops on Codex's own "Trust this folder?" prompt and waits for a human. Observed repeatedly on 2026-10-02 across sap-cpq-ci, score-varicent, sap-im-doc-survey and others. Each one needs the captain to press enter before any work starts, which is precisely the interruption the Claude path was built to prevent.
The captain's own workaround, applied locally on 2026-10-02: add a trust entry for the treehouse root to ~/.codex/config.toml, which covers every current and future worktree in one line:
[projects."~/.treehouse"]
trust_level = "trusted"
Per-project entries are not enough, because workers never run in the project clone - they run in a disposable worktree under a different root, and Codex resolves trust from the path it is launched in.
Worth contributing upstream rather than keeping local, since any firstmate home dispatching Codex workers into treehouse worktrees hits this on every new worktree. The fix likely belongs beside the existing Claude pre-registration in the spawn path, so both verified harnesses behave the same way; a configuration note in the documentation would be a weaker second choice because it leaves the asymmetry in the code."
What Changed
Adds
bin/fm-codex-trust.sh. It trusts only the exact launch directory: either an isolated linked worktree of the given project or a seeded secondmate home. Never an ancestor, the treehouse root or a primary checkout. It writes the entry into${CODEX_HOME:-~/.codex}/config.tomlthrough Codex's private stdioapp-serverconfig API (config/read+config/value/write), not by editing the TOML directly. It refuses in these cases:CODEX_HOMEis relativeuntrustedentryA version conflict or a lost concurrent write is re-read and retried, up to 3 attempts. The script reads the entry back to confirm it was saved before reporting success. Its
app-serveris always shut down, with SIGKILL if it hasn't stopped 2 seconds after SIGTERM.bin/fm-spawn.shnow runs this helper before every codex worker or secondmate launch. It refuses the spawn if the helper fails, the same way the Claude path does. It also passes the resolvedCODEX_HOMEinto the launch command, so registration and launch read the same store.Test and doc changes:
tests/lib.shgives each suite a privateCODEX_HOMEand puts a config-onlycodexwrapper (tests/codex-config-fixture.js) first on PATH, so non-live spawns never need an installed codex or touch the operator's real store.fm_live_gateundoes both for live guards.tests/fm-codex-trust.test.sh(portable) and the opt-intests/fm-codex-workspace-trust-live-e2e.test.sh, both registered inbin/fm-test-run.sh.CONTRIBUTING.md,docs/orca-backend.mdanddocs/verification/runtime-backends.mdnow describe Codex pre-registration.🤖 Generated with Claude Code
Risk Assessment
Testing
I re-ran the Codex trust unit suite and the live end-to-end check against the real codex-cli 0.159.3: a registered worktree reached the composer without the trust prompt. I then re-drove the round-5 concurrency failure live against the real app-server: 138 registrations raced into one shared disposable store, and none were refused or lost. The secondmate-harness suite from the latest CI fix passes, and the operator's real Codex store was left byte-identical. Two scenarios were checked only by the unit suite, with fixtures, so they are untested live: the fail-closed refusals and spawn refusal, and the kill of a server that stalls on shutdown. All temporary directories were removed.
Evidence: Real-codex concurrent registration race (2-way and 4-way)
Source: Real-codex concurrent registration race (2-way and 4-way)
n=2 rounds=25 refused=0 missing_entries=0 n=4 rounds=12 refused=0 missing_entries=0 project tables: 99 trusted: 99 model = "gpt-5" (pre-existing key preserved)Evidence: 8-way stress race
Source: 8-way stress race
Evidence: Live Codex TUI trust e2e
Source: Live Codex TUI trust e2e
Evidence: fm-codex-trust suite
Source: fm-codex-trust suite
Evidence: fm-secondmate-harness suite
Source: fm-secondmate-harness suite
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-codex-trust.sh:95- Every codex spawn and relaunch (bin/fm-spawn.sh:4430-4442) now refuses unless node is present and the installed codex answerscodex app-server --listen stdio://with theconfig/read(includeLayers) andconfig/value/write(expectedVersion) API (bin/fm-codex-trust.sh:95-132). Before this change, a codex install without that API launched and stopped on the trust dialog. Now it cannot launch codex workers at all. This fail-closed choice matches the Claude guard that the intent calls good behaviour, and it is recorded as verified only on codex-cli 0.159.3 (docs/verification/runtime-backends.md). No action is needed; this is noted so operators on older Codex builds know about the new version floor.✅ No issues found.
bin/fm-codex-trust.sh:38-ownedRegularuseslstatand requires a regular file. As a result, a${CODEX_HOME:-~/.codex}/config.tomlthat is a symlink causes the helper to refuse (lines 41, 87, 114 and 122). Becausebin/fm-spawn.sh:4549-4552treats that refusal as fatal, every codex worker, scout and secondmate spawn fails on that host. Symlinks are the normal layout when dotfile managers (stow, chezmoi, home-manager) manageconfig.toml. Before this change such a host launched codex and showed the trust prompt. Now it cannot launch codex at all, and the error claims the worker 'would wedge on the trust dialog'. Failing closed on symlinked stores is a deliberate, documented choice, and probably protects against Codex's atomic replace overwriting the link. The user should still confirm that blocking every codex spawn is preferred over, for example, falling back to the old interactive prompt with a warning, or naming the symlink in the error. The remedy changes product behaviour, so it needs the user's authorization.bin/fm-codex-trust.sh:113- The trust write usesexpectedVersionfrom a singleconfig/read, and any version conflict is a hard refusal with no retry. The spawn fails atbin/fm-spawn.sh:4549. Two firstmate homes on one host share~/.codex/config.toml: a primary and a local secondmate dispatching codex workers at the same moment, or a running codex session rewriting its config. In that case one spawn can lose the race and be refused, even though a fresh read-and-write would succeed. The siblingbin/fm-claude-trust.shre-reads and retries within bounded attempts in the same situation. A bounded re-read and retry on conflict would correct this within scope. However,tests/fm-codex-trust.test.sh(the 'version conflict refuses registration' case) deliberately asserts the refusal, so whether to retry is the user's call.🔧 Fix applied.
1 warning still open:
bin/fm-codex-trust.sh:38-ownedRegularuseslstatand requires a regular file. As a result, a${CODEX_HOME:-~/.codex}/config.tomlthat is a symlink causes the helper to refuse (lines 41, 87, 114 and 122). Becausebin/fm-spawn.sh:4549-4552treats that refusal as fatal, every codex worker, scout and secondmate spawn fails on that host. Symlinks are the normal layout when dotfile managers (stow, chezmoi, home-manager) manageconfig.toml. Before this change such a host launched codex and showed the trust prompt. Now it cannot launch codex at all, and the error claims the worker 'would wedge on the trust dialog'. Failing closed on symlinked stores is a deliberate, documented choice, and probably protects against Codex's atomic replace overwriting the link. The user should still confirm that blocking every codex spawn is preferred over, for example, falling back to the old interactive prompt with a warning, or naming the symlink in the error. The remedy changes product behaviour, so it needs the user's authorization.🔧 **Test** - 2 issues found → auto-fixed (2) → no changes applied ✅
tests/fm-control-herdr-smoke.test.sh- The Herdr control smoke test fails locally (Herdr 0.9.3) at the 'composer is not proven empty' assertion: the control plane now reports 'composer visibly holds pending text'. Base commit 53b5bc1 fails identically, so this branch didn't cause it. As the verification plan asks, the PR should not claim this test passes until upstream CI reports success.bash tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex-cli 0.159.3: real config API writes, unregistered vs registered TUI counterfactual)Ran fm-spawn.sh codex ship spawns against the real codex app-server (with fake tmux and treehouse for the pane mechanics), then launched the real codex TUI in a pty in the spawned worktree and in the primary checkoutSpawn refusal paths with real codex: worktree marked explicitly untrusted, and no codex on PATH (the CI ENOENT case)Ran bin/fm-codex-trust.sh by hand against the real codex: a linked worktree, then refusal cases (primary checkout, ancestor directory, worktree of an unrelated project, $HOME, untrusted entry, relative CODEX_HOME, symlinked config.toml)bash tests/fm-codex-trust.test.sh(includes the stalled-shutdown SIGKILL regression)bash tests/fm-spawn-worktree-settle.test.sh,tests/fm-secondmate-lifecycle-e2e.test.sh,tests/fm-live-gate.test.sh,tests/fm-test-fixture-cleanup.test.shbash tests/fm-control-herdr-smoke.test.shon its private fm-lab-control-smoke-* Herdr session, then the same test from agit archiveexport of base commit 53b5bc11 for comparisonCompared the SHA of the real ~/.codex/config.toml before and after🔧 No changes applied.
3 issues (2 errors, 1 info) still open:
bin/fm-remote-entrypoint.sh:139- The remote-secondmate suites still write the operator's real ~/.codex/config.toml. tests/lib.sh exports a suite-private CODEX_HOME and puts a config-only codex wrapper on PATH. But bin/fm-remote-entrypoint.sh runsunset HOME, resolves the real account home, composes an operator PATH that includes /opt/homebrew/bin, and re-execs the job underenv -i. So the remote secondmate spawn's bin/fm-codex-trust.sh uses the real codex and the real store. Observed live: running tests/fm-remote-secondmate-parent-binding.test.sh (exit 0) added a/private/var/folders/.../fm-remote-parent-binding.M2qcPN/remote-hometrusted entry. The tester removed that one entry. Three earlier leaked entries (fm-remote-parent-binding.qh2R7x, fm-remote-secondmate-e2e.BQWWQT, fm-remote-trace-context.65MynB) remain in the operator's store for the operator to remove. On CI runners without codex, the same path probably still hitsspawn codex ENOENTfor the remote suites. A fix needs either a test seam that carries CODEX_HOME and the fixture through the remote job environment, or remote suites that provide a fake account home or codex. Both touch the remote-entrypoint isolation contract, so firstmate should decide.tests/fm-control-herdr-smoke.test.sh- The Herdr control smoke still fails locally (Herdr 0.9.3) at the 'composer is not proven empty' assertion, exactly as on base 53b5bc1. Every earlier check, including the Codex relaunch, now passes. The PR should not claim this test passes until upstream CI reports success.spawned trustlive harness=codex; the pane shows the launch brief submitted and theAsk Codexcomposer, and grep finds 0 trust-dialog markers. (The dummy API…# operator commentandmodel_reasoning_effortand adds only[projects."<exact worktree>"] trust_level = "trusted". The live e2e confirms the unregistered pr…bin/fm-test-run.sh tests/fm-codex-workspace-trust-live-e2e.test.sh→ codex-trust-live-e2e.log (exit 0)tests/fm-remote-secondmate-parent-binding.test.shexited 0, but ~/.codex/config.toml gained a `/private/var/folders/.../fm-remote-parent-binding.M2qcPN/r…HOME=<isolated> FM_CODEX_WORKSPACE_TRUST_LIVE=1 bash bin/fm-test-run.sh tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex-cli 0.159.3 against a disposable CODEX_HOME)Lab home viabin/fm-lab-home.sh create; on a privatetmux -L fm-labsocket, realbin/fm-spawn.sh trustlive <lab project> --scout --harness codex --backend tmuxwith a disposable CODEX_HOME and dummy API key; captured the worker pane and the lab config.tomlSame lab: replaced config.toml with malformed TOML, thenbin/fm-spawn.sh trustrefuse ... --harness codex; checked for the refusal, an unchanged store, no codex process and no task metabash tests/fm-control-herdr-smoke.test.shat HEAD (private fm-lab-control-smoke-* Herdr session)bash tests/fm-control-herdr-smoke.test.shin a temporary base-commit (53b5bc11) worktree for comparisonbash tests/fm-remote-secondmate-parent-binding.test.sh, with shasum of ~/.codex/config.toml taken before and afterTeardown: killed the lab tmux server; removed the lab home, the treehouse worktree the spawn created, the base worktree, and the leftover test scratch dirs; removed only the ~/.codex entry the remote suite added during this run🔧 Fix applied.
✅ Re-checked - no issues remain.
bash tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex 0.159.3: a fresh unregistered checkout shows the trust gate; the registered worktree reaches the composer; TOML preserved; idempotent; malformed store refused)bash tests/fm-codex-trust.test.sh(eligibility, preservation, fail-closed, version conflict, stalled-shutdown SIGKILL, spawn forwards CODEX_HOME, spawn refuses)Lab:bin/fm-lab-home.sh create+ privatetmux -L fm-lab+FM_SPAWN_NO_GUARD=1 bin/fm-spawn.sh codexlive <lab>/proj --mode local-only --yolo off --harness codexwith the real codex and an isolated CODEX_HOME holding a dummy keyLab adversarial: same spawn (codexrefuse) against a malformedconfig.tomlin the lab CODEX_HOMEbash tests/fm-remote-secondmate-parent-binding.test.sh,fm-remote-secondmate-trace-context,fm-remote-secondmate-lifecycle-e2e,fm-spawn-compact-adviser-disable-remote,fm-secondmate-lifecycle-e2ewith real codex on PATH, andshasum ~/.codex/config.tomlbefore and afterbash tests/fm-codex-hook-layer-live-e2e.test.sh,bash tests/fm-harness-liveness-drift-live-e2e.test.shfollowed by a real-store hash checkSimulated CI PATH (homebrew tools symlinked without codex):fm-secondmate-lifecycle-e2e,fm-spawn-compact-adviser-disable,fm-codex-trust,fm-remote-secondmate-parent-binding✅ No issues found.
bash tests/fm-codex-workspace-trust-live-e2e.test.shdrives real codex 0.159.3 TUI (suite-fm-codex-workspace-trust-live-e2e.log)Lab:bin/fm-lab-home.sh create $LAB+ privatetmux -L fm-labsession (FM_HOME=$LAB, CODEX_HOME=$LAB/codex with dummy API key, TREEHOUSE_ROOT=$LAB/treehouse)bin/fm-spawn.sh scout1 $LAB/projects/alpha --scout --harness codex(real codex + real treehouse) thentmux capture-paneof fm-scout1 and read of $LAB/codex/config.tomlbin/fm-home-seed.sh mate1 <home> alpha+bin/fm-spawn.sh mate1 <home> --harness codex --secondmate, then pane capture and store readCODEX_HOME=$LAB/codex-broken bin/fm-spawn.sh scout2 ... --scout --harness codexagainst malformed config.toml (refusal path)bash tests/fm-codex-trust.test.shbash tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex TUI counterfactual)bash tests/fm-secondmate-harness.test.shbash tests/fm-remote-secondmate-parent-binding.test.shbash tests/fm-secondmate-lifecycle-e2e.test.shshasum -a 256 ~/.codex/config.tomlbefore and after all runs (read-only)bin/fm-codex-trust.sh:135- Live races against the real codex-cli 0.159.3 app-server show that concurrent registrations into one shared CODEX_HOME mostly collide through a lost update, not a configVersionConflict. config/value/write returns status ok, then another writer's replace drops the entry, and the post-loop read-back refuses with 'Codex configuration did not retain workspace trust'. The bounded retry added for review-2 only catches configVersionConflict, so this path is never retried. Observed: 2 of 25 two-way races and 4 of 12 four-way races refused at least one spawn (race-postcheck-n2.log, race-postcheck-n4.log). This is fail-closed: no helper reported success and then lost its entry. Fix within the recorded decision: move the read-back verification into the bounded attempt loop, so a missing entry after an ok write re-reads and rewrites, up to the same 3 attempts, then refuses. Add a fixture mode where the write succeeds but the next read omits the entry, and assert retry-then-success and retry-past-bound refusal.ℹ️
bin/fm-codex-trust.sh:91- Several helpers started at once against a never-used CODEX_HOME can fail with 'Codex config server exited (1)', because codex app-server's own sqlite state initialization races ('failed to initialize sqlite state runtime'). This is Codex-side and only affects a cold store; a real ~/.codex is already initialized. The spawn fails closed. For information only.🚨 live validation verdict: no-go (7 of 7 scenarios were driven live against the product); failed: Adversarial: concurrent codex spawns sharing one Codex store all register via the bounded re-read/retry (user decision review-2)
Live validation: ❌ no-go - 7 of 7 scenarios driven live against the product
bash tests/fm-codex-workspace-trust-live-e2e.test.sh→ live-e2e.logbash tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex-cli 0.159.3: TOML preserved, repeat run is a no-op, the unregistered checkout shows the trust gate, the registered worktree reaches the composer, malformed TOML is refused)bash tests/fm-codex-trust.test.sh(fixture: retry on version conflict clears/persists, stalled-shutdown SIGKILL, secondmate eligibility, spawn registers/refuses)race-and-guards.sh: 8 concurrent real-codex registrations into a cold shared CODEX_HOME, plus primary checkout, treehouse root and explicit-untrusted refusalsrace-warm-store.shx3 (N=8 warm store),race-warm-n2.sh/race-warm-n4.shx5 each,race-postcheck.sh <root> 2x25 andrace-postcheck.sh <root> 4x12: concurrent real-codex registrations with a post-check that every reported success persistedLab:bin/fm-lab-home.sh create, tmux -L fm-lab primary shell,bin/fm-spawn.sh scout-trust1 $LAB/projects/proj --scout --harness codexwith CODEX_HOME=$LAB/codex and TREEHOUSE_ROOT=$LAB/th; captured the worker pane and the storeLab: same spawn (scout-trust2) with a malformed$LAB/codex/config.toml; checked the exit code, diagnostic, store contents and that codex was not the pane's foreground commandshasum ~/.codex/config.tomlbefore and after all runs (unchanged), then lab teardown (tmux -L fm-lab kill-server,fm-lab-home.sh teardown, rm -rf)🔧 Fix applied.
✅ Re-checked - no issues remain.
bash tests/fm-codex-trust.test.sh(conflict retry and lost-update retry, each clearing and past the bound; stalled-shutdown SIGKILL; fail-closed stores; spawn refuses before launch)bash tests/fm-codex-workspace-trust-live-e2e.test.sh(real codex TUI: an unregistered checkout stops at the trust prompt, a registered worktree reaches the composer with no input)Real codex app-server race:bin/fm-codex-trust.shrun concurrently on fresh linked worktrees sharing one disposable warmed CODEX_HOME, 2-way x25, 4-way x12 and 8-way x5bash tests/fm-secondmate-harness.test.sh, with a checksum of the operator's ~/.codex/config.toml taken before and after✅ **Document** - passed
✅ No issues found.
✅ No issues found.
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ No issues found.
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
✅ No issues found.
✅ No issues found.