Skip to content

Fix skill discovery, sandbox reads and abandoned approvals (reconcile-worktrees post-mortem) - #2620

Merged
jonathanKingston merged 2 commits into
mainfrom
claude/reconcile-worktrees-postmortem
Sep 9, 2026
Merged

Fix skill discovery, sandbox reads and abandoned approvals (reconcile-worktrees post-mortem)#2620
jonathanKingston merged 2 commits into
mainfrom
claude/reconcile-worktrees-postmortem

Conversation

@jonathanKingston

@jonathanKingston jonathanKingston commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the three findings from the 2026-09-09 post-mortem of /reconcile-worktrees stalling inside Copse (Codex over ACP, thread in a linked worktree, tools via the bridged run_shell).

1. Skill discovery ignored ~/.codex/skills

  • .codex is now a skill container at both scopes (~/.codex/skills, <workspace>/.codex/skills), last in precedence. SKILL_CONTAINER_DIRS documents the order.
  • Project-scope roots are sorted by container precedence instead of readdir order, so first-writer-wins actually follows the documented order.
  • userSkillRoots(home) is exported and a setUserSkillsHomeForTest override isolates suites from the developer's real skills (this machine's ~/.codex/skills/reconcile-worktrees was shadowing the bundled copy in an existing test, exactly as user-over-bundled intends).

2. run_shell could not read the skill directory or the primary checkout

  • Skill directory, read-only, per thread. Invoking a skill grants that thread's sandboxed shell read access to the skill directory (realpath and discovered spelling) plus validated paths frontmatter entries. New thread-read-roots.ts (session-only ledger) and skill-read-roots.ts (validation: relative only, no .., no credential files/dirs, no home/root, symlink escapes only for trusted sources). The seatbelt overlay adds them to allowRead only; the shell-scope classifier now takes multiple contained read roots so read-only commands naming them auto-run inside the sandbox. The invoked-skill prompt says what is readable and which paths entries were refused.
  • sed is admitted as structurally read-only in its provable filter shape (no -i/-f/unknown options, no w/W/r/R/e letters in the script), so the post-mortem's sed -n '1,320p' ~/.codex/skills/.../SKILL.md no longer prompts.
  • Primary checkout, read-only. A linked-worktree thread can read the shared primary checkout (never write it). The allow is per top-level child with the shared .git, the thread's own nested worktree and nested sibling worktrees carved out, because a bare primary/** would let Linux bind ordering shadow the .git/objects write binds. The primary's HEAD, index, ORIG_HEAD/FETCH_HEAD and every registered sibling's admin dir are readable, so git worktree list --porcelain lists everything with real HEADs and git -C <primary> … works. The security trade-off is written up in primaryCheckoutReadPaths in config.ts.
  • Docs: docs/cursor-plugins.md gains a discovery-roots and frontmatter section (including paths); docs/supply-chain-security.md and docs/acp-agents.md updated.

3. Abandoned MCP calls cancelled pending approvals

  • The bridge aborts an abandoned call (socket close, notifications/cancelled routed through a bridge-wide in-flight map since stateless mode gives each POST its own server, or the SDK's per-request signal) with an AbandonedCallAbort reason. AbortSignal.any propagates that reason, so the turn and session aborts keep cancelling as before.
  • approval.ts treats that reason as a detach: the prompt stays open with a parked stand-in waiter, the caller is rejected with ApprovalPendingError ("still pending, retry the exact same call"), and the user's eventual verdict is stored for ten minutes keyed by thread, execution root and prompt. An identical request replays it once without prompting; a retry that arrives while the prompt is still open joins it and retires the stand-in, so one answer never authorises two runs. Turn end and transport teardown still dismiss parked prompts and store nothing.

Review fixes

  • Enforce the 400-entry primary-checkout read allowlist bound even for wide directories; the regression returned 502 entries before the fix.
  • Share active-run identity through a lightweight module, keeping sandbox configuration free of desktop/native imports. This fixes the ACP worker bundle and stdout protocol checks without changing thread identity semantics.

Tests

  • Finding 1: skills-registry.test.ts (.codex project and user discovery, precedence, userSkillRoots).

  • Finding 2: thread-read-roots.test.ts, skill-read-roots.test.ts (paths validation), skill-prompt.test.ts (grant + prompt text), security/shell-scope.test.ts (classifier waiver), shell-guard/src/shell-argv.test.ts (sed), project-sandbox/config.test.ts (overlay shape, carve-outs, thread roots), project-sandbox/worktree-integration.test.ts (real ASRT: primary readable, not writable, nested sibling denied, git -C primary, git worktree list).

  • Finding 3: approval.test.ts (park, replay, single use, join, TTL, scoping, turn end, headless) and acp-native-bridge.test.ts (socket drop then replay; MCP cancel returns the pending error to the agent).

  • Review regressions: bounded filesystem enumeration; existing ACP probe/session worker bundle, stdout protocol, thread identity, and read-root tests.

  • Local validation: full pnpm run check passed 8,883 tests and pnpm run build passed on the pushed head; actual macOS seatbelt worktree integration included.

  • After Support directory-scoped AGENTS.md instructions #1976 and feat(security): run a thread unattended inside a hardened container #2348 merged separately, the clean local merge with current main (d231c026b) passed the full check again: 9,109 tests, zero failures/skips, plus a successful build.

The review fixes change internal enumeration and dependency wiring, with no renderer/DOM changes.

Not touched: the audit script under ~/.codex/skills (separate task).

🤖 Generated with Claude Code

jonathanKingston and others added 2 commits September 9, 2026 04:02
…econcile-worktrees post-mortem

Three findings from the 2026-09-09 post-mortem of /reconcile-worktrees stalling
inside Copse when driven by Codex over ACP.

1. Skill discovery ignored ~/.codex/skills. `.codex` joins the container list
   at both scopes, last in precedence; project-scope roots are now sorted by
   that precedence instead of readdir order so the documented order is true.
   `userSkillRoots()` and a test-only home override make the user scope
   testable without the developer's real skills leaking in.

2. run_shell could not read the invoked skill's directory or the primary
   checkout. Invoking a skill now grants that thread read-only sandbox access
   to the skill directory plus validated `paths` frontmatter entries
   (thread-read-roots.ts / skill-read-roots.ts); the seatbelt overlay adds them
   to allowRead only and the shell-scope classifier treats them as contained
   read roots, so read-only commands naming them auto-run in the sandbox. `sed`
   is admitted as structurally read-only in its provable filter shape. A
   linked-worktree thread can now read (never write) the shared primary
   checkout, with the shared .git, its own nested worktree and nested sibling
   worktrees carved out per child so Linux bind ordering cannot shadow write
   binds; the primary's HEAD, index and sibling admin dirs are readable so
   `git worktree list` and `git -C <primary>` work.

3. An abandoned bridged MCP call cancelled its pending approval. The bridge now
   aborts an abandoned call (socket close or notifications/cancelled) with an
   AbandonedCallAbort reason; the approval service parks the prompt instead of
   cancelling it, rejects the caller with ApprovalPendingError telling the
   agent to retry the same call, and replays the eventual verdict once to an
   identical request (same thread, execution root and prompt) within ten
   minutes. Turn end still dismisses parked prompts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🖥️ PR preview

@copse-release-bot

Copy link
Copy Markdown
Contributor

Reference screenshots ready for review

Review GitHub’s image diffs in screenshot PR #2622.
Merge it (or enable auto-merge) to apply the accepted PNGs to claude/reconcile-worktrees-postmortem.

Rendered for 73ed4f26b9a9 by CI run 34307713822; the immutable artifact is reference-screenshot-candidates-34307713822.

If this source branch moves, a later successful render closes the stale review PR and replaces this link.

@jonathanKingston
jonathanKingston merged commit a288035 into main Sep 9, 2026
24 checks passed
@jonathanKingston
jonathanKingston deleted the claude/reconcile-worktrees-postmortem branch September 9, 2026 03:59
github-actions Bot added a commit that referenced this pull request Sep 9, 2026
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