Skip to content

fix(lanes): harden codex lanes — zsh-safe timeout, scratch dir, sandbox and IPC handling - #24

Closed
raymond-w-ko wants to merge 1 commit into
DannyMac180:mainfrom
raymond-w-ko:fix/lane-hardening-v5
Closed

raymond-w-ko wants to merge 1 commit into
DannyMac180:mainfrom
raymond-w-ko:fix/lane-hardening-v5

Conversation

@raymond-w-ko

Copy link
Copy Markdown

Summary

Consolidates onto v5.0.0 the fixes that contributors proposed against v4 in #5, #9, #12, #14, #15, #17 and issues #13, #23. All of those still describe real defects on main, but the v5 rewrite (fable-implementersol-implementer, per-task effort, duplicated lane files) means none of the original hunks apply. This PR re-implements the portable parts in both lane files plus the orchestration skill, so the individual PRs can be closed in favour of it. Credit to @ericlitman, @wooter, @pandaofwild, @xiaolai, @sherySJ, @nerdstalker and @tr3lane for the diagnoses; co-author trailers are on the commit.

Changes

Timeout wrapper (#12, #13, #17, with the Windows timeout.exe note from #4). ${T:+$T 600} is a single argv word under zsh, so the lane died with exit 127 before codex started and the documented cap never applied. Replaced with a set -- "$T" -k 15 540 / "$@" prefix that works under bash, zsh and sh, validated as GNU coreutils (--version | grep -qi coreutils), and kept strictly inside the Bash tool's default 600000 ms ceiling so the shell timeout, not the tool, kills codex.

Exit classification and IPC mismatch (#17, #23). RC=$? is captured and stderr goes to a per-lane file. 124/137 → timeout; other non-zero → new execution-error status with the exact error, never inferred as auth; the code-mode host / IPC frame / code_mode_host_duration_ns signature with exit 0 → unavailable with path/version diagnostics and an explicit no-retry rule. SKILL.md tells the architect that these unavailable cases are host problems and that re-routing Luna ↔ Sol will fail identically.

Scratch dir and no backgrounding (#14). mktemp -d "${TMPDIR:-/tmp}/codex-lane.XXXXXX" replaces the two mktemp -t calls whose literal XXXXXX survives on BSD and let parallel lanes pick up each other's spec. The doc now forbids recovering the path by glob or sidecar file, and forbids launching codex in the background and ending the turn.

Sol long runs. Sol's 1800 s foreground cap was never enforceable under the tool ceiling. Sol now caps foreground at 540 s like Luna and gets one bounded detached path for max/ultra: subshell writes $LANE/rc, pid captured, 1740 s cap, foreground polling in 550 s loops, deadline kill via pkill -f "$LANE". The lane still never ends its turn with codex running.

Sandbox (#15, #5). Precondition table (no network, no docker socket, no commits from a linked worktree), the tested .git-writable hook escape warning, and the write-denial handling: unavailable by default, one retry without --sandbox only when the spec carries sandbox-fallback: allowed, always disclosed via a SANDBOX: report line.

Consistency (#9). Lane table now says the routine lane escalates after failing twice, matching the deciding rule and fable-advisor.md. Lane tools: trimmed to Bash, Read.

Verification

  • claude plugin validate . passes.
  • Wrapper probe under bash and sh (zsh not installed here): cap fires with exit=124; with no GNU timeout the WARN line prints and the command runs uncapped. The zsh branch was verified by the reporter and a second commenter on codex-implementer: timeout guard is a no-op under zsh (exit 127 before codex runs) #13.
  • diff agents/codex-implementer.md agents/sol-implementer.md shows only the intended differences (slug, effort table, caps, the Sol detached subsection, Sol-specific report lines).
  • Fable advisor review: ship.

Related upstream items

This PR supersedes #12, #14, #15, #17 and the codex half of #4, and fixes #13 and #23 (portable parts). #5 and #9 are folded in. Not included: the <example> blocks from #10 (still open, applies to all three agents), the cursor lane from #7, and the Smithers pack from #6.

🤖 Generated with Claude Code

…ox and IPC handling

Consolidates onto v5 the still-valid fixes proposed against v4 in upstream
PRs DannyMac180#5, DannyMac180#9, DannyMac180#12, DannyMac180#14, DannyMac180#15, DannyMac180#17 and issues DannyMac180#13, DannyMac180#23, which no longer apply
cleanly after the v5 rewrite.

- Replace the `${T:+$T 600}` timeout prefix with a positional-parameter
  wrapper that works under bash, zsh and sh, validated as GNU coreutils,
  capped at 540 s inside the Bash tool's default 600000 ms ceiling (DannyMac180#12,
  DannyMac180#13, DannyMac180#17, DannyMac180#4).
- Capture the exit status and stderr; classify 124/137 as timeout, other
  non-zero as the new `execution-error` status, and the code-mode IPC
  mismatch signature as `unavailable` with diagnostics and no retry (DannyMac180#17,
  DannyMac180#23).
- One private scratch dir per lane via `mktemp -d`; forbid globbing or
  sidecar files to recover it; forbid backgrounding codex and ending the
  turn (DannyMac180#14).
- Sol only: a bounded detached path for runs longer than the tool ceiling,
  with pid capture, foreground polling and a deadline kill.
- Sandbox preconditions (network, docker, worktree commit), the
  `.git`-writable escape warning, and the sandbox-denial handling with the
  explicit `sandbox-fallback: allowed` opt-in (DannyMac180#15, DannyMac180#5).
- Escalation trigger says "twice" consistently; lane tools trimmed to
  `Bash, Read` (DannyMac180#9).

Co-authored-by: ericlitman <ericlitman@users.noreply.github.com>
Co-authored-by: wooter <wooter@users.noreply.github.com>
Co-authored-by: pandaofwild <pandaofwild@users.noreply.github.com>
Co-authored-by: xiaolai <xiaolai@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tr3lane

tr3lane commented Sep 14, 2026

Copy link
Copy Markdown

Author of #4 here. #24 is a clear improvement on v5 main and I'd like to see it merged. Two Windows notes from re-testing today, both measured, neither blocking.

1. The timeout prefix caps the shim, not the run, on Windows

This PR takes the timeout.exe half of #4, validating GNU coreutils instead of trusting command -v timeout. That is the right call. The other half of that finding was that on Windows even a genuine GNU timeout under-delivers: codex is an npm shim (sh to node), and timeout/kill reach only the direct child, so the node worker survives the TERM and the KILL.

That matters more after this PR, not less. On Git Bash, /usr/bin/timeout is real coreutils:

$ timeout --version | head -1
timeout (GNU coreutils) 8.32

So the new --version | grep -qi coreutils gate passes, T gets set, and the lane runs timeout -k 15 540 codex exec ... believing it is capped. It isn't. At the deadline the shim dies and the worker keeps running. Before this change the Windows path at least failed loudly at 127; now it fails quietly.

What works is branching the capper by platform and tree-killing:

case "$(uname -s)" in
  MINGW*|MSYS*|CYGWIN*) ;;   # skip timeout entirely; taskkill //T //F on the win PID
  *) ;;                      # validated GNU timeout, exactly as this PR has it
esac

2. The sandbox fallback adopted from #5 does not recover Windows

Measured today on Windows 11 / Git Bash against codex-cli 0.154.0 (npm latest), with 0.144.6 as a control. Trivial spec: create one file containing one word.

route codex codex reported result
--sandbox workspace-write 0.144.6 sandbox: read-only exit 0, no write
--sandbox workspace-write 0.154.0 sandbox: read-only exit 0, no write
no --sandbox, -c sandbox_mode=workspace-write 0.154.0 sandbox: read-only exit 0, no write
trust_level = "trusted" project, --sandbox workspace-write 0.154.0 sandbox: read-only exit 0, no write

~/.codex/.sandbox was never created on any of those runs, so the cached SetNamedSecurityInfoW ACE failure described in #5 is not the mechanism on this host. Codex reports sandbox: read-only regardless of which flag or config key is set, whether the project is trusted, and which version is installed. There is no OS-enforced sandbox on Windows for it to downgrade from.

The effect on this PR as written: a Windows operator hits the read-only signature, gets STATUS: unavailable, is told to resend the spec with sandbox-fallback: allowed, and that retry returns the identical result. Worth either platform-gating that branch or documenting it as a macOS/Linux remedy.

The approach that holds up here is --sandbox read-only on Windows by design, with codex returning a git apply-able patch in its final message under a STATUS: proposal, so the lane stays a working cross-vendor reviewer instead of a dead writer.

Both points are in #4 against v4 if that patch is useful as a reference, though it needs a rebase onto v5 regardless.

@raymond-w-ko

Copy link
Copy Markdown
Author

Closing: I am carrying these changes on my fork instead of proposing them upstream. Sorry for the noise.

@raymond-w-ko
raymond-w-ko deleted the fix/lane-hardening-v5 branch September 14, 2026 03:25
raymond-w-ko added a commit to raymond-w-ko/fable-advisor that referenced this pull request Sep 14, 2026
…e the Astra lane

This checkout is a personal fork of DannyMac180/fable-advisor and is no
longer proposing changes upstream (upstream PR DannyMac180#24 was closed). The README
still read as the upstream project: its install commands registered the
upstream marketplace, so a reader of this fork would install a plugin
without the fork's lanes, and nothing in the prose mentioned the new
browser lane.

README.md:
- Tagline now includes Astra driving the browser.
- Fork banner under the tagline naming upstream and origin, summarising
  what the fork carries (the v5.1 lane hardening and the `astra-operator`
  lane with the `computer-use` skill), and saying to install from origin.
- Install block registers `raymond-w-ko/fable-advisor`, with a note to
  remove an existing upstream marketplace entry of the same name first so
  the install resolves to the fork.
- New "What's in v5.1 (this fork)?" paragraph ahead of the upgrade notes,
  describing the Astra lane, the skill, and the `playwright_chrome`
  prerequisite in `~/.codex/config.toml`.

.claude-plugin/plugin.json and .claude-plugin/marketplace.json: each
description gains a sentence about the astra-operator lane and the
computer-use skill so the plugin listing reflects what it ships. Owner
and author fields are left as upstream's; the fork does not claim the
project.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tr3lane

tr3lane commented Sep 14, 2026

Copy link
Copy Markdown

Correction to my point 1. I was wrong, and I should have measured before posting it.

I re-tested the timeout wrapper on codex-cli 0.154.0 (Git Bash, /usr/bin/timeout = GNU coreutils 8.32) and it does not orphan the worker. On this version codex is a three-level chain: the sh launcher execs node bin/codex.js, which spawns the native @openai/codex-win32-x64 binary. Running timeout -k 3 5 codex exec ... against a live run:

  • +3s mid-run: the native codex.exe worker is up, confirmed by PID
  • cap fires at 5s: RC=124
  • immediately after, and again at +6s: that codex.exe PID is gone, and there is no codex.exe anywhere

So GNU timeout takes the whole chain down, native worker included. This PR's timeout -k 15 540 wrapper caps and cleans up correctly on Windows, and my "reaches only the shim, the worker keeps running" line was carried over from #4 against an older codex (0.144.x) without re-verifying it. I have not re-tested 0.144.x's process behaviour, so I can't say whether codex changed or the claim never reproduced there, but on current codex the platform-specific tree-kill is not needed, and this PR is fine as written on that point.

Point 2 (sandbox) stands as measured: --sandbox workspace-write reports sandbox: read-only on Windows regardless of flag, config key, project trust, or version, ~/.codex/.sandbox is never created, and the sandbox-fallback: allowed retry returns the same read-only. On Windows both codex lanes are read-only reviewers rather than writers, and that fallback line can't recover them. Read-only also blocks process execution, so codex can't run a verification command there either. That one is Windows-only, so it may not matter for your setup.

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.

codex-implementer: timeout guard is a no-op under zsh (exit 127 before codex runs)

2 participants