fix(scratch): fall back to $TMPDIR when /tmp cannot host the scratch root - #1398
Conversation
…root Under Claude Code's macOS sandbox only $TMPDIR (/tmp/claude-<uid>) is writable, so every skill's cross-invocation scratch setup aborted with "Operation not permitted" at /tmp/compound-engineering-<uid>. #1305 fixed the per-run mktemp forms and deferred this durable root. Every shell preamble, the six identical peer-job-runner.py copies, and ce-work's unit_workspace_state.py now probe the /tmp root (creatable, not a symlink, owned, writable) and otherwise use ${TMPDIR:-/tmp}/compound-engineering-<uid>, in the same order, so later invocations resolve the same root. Writability is probed with [ -w ] / os.access because a pre-existing root passes mkdir -p and chmod under the sandbox yet refuses the first inner write. Unsandboxed behavior is unchanged. ce-handoff resume enumerates both candidate roots. Fixes #1294 Claude-Session: https://claude.ai/code/session_014wod7UqLtRtt91osnKtydm
PR SummaryCursor Bugbot is generating a summary for commit a2c9866. Configure here. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2c9866ceb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- peer-job-runner: status/wait/result/reap locate an existing job under either candidate root (/tmp or $TMPDIR fallback), so a job started from a sandboxed session is still found by a later unsandboxed invocation - ce-work: run_dir prefers an existing run under either candidate root before defaulting to the creation root - tests: cover both-roots lookup; compare resolved paths in the Windows Git Bash fallback case (forward-slash output vs path.join) Claude-Session: https://claude.ai/code/session_014wod7UqLtRtt91osnKtydm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01d021eb08
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- ce-work: propagate the discovered run root through locked_manifest, init re-entry, and discover_resume_run, so status/prepare/sync-job/ integrate/resume operate on a run recorded under the other candidate root instead of reconstructing its path beneath the creation root - test: locked_manifest opens the fallback-root run Claude-Session: https://claude.ai/code/session_014wod7UqLtRtt91osnKtydm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3350de8a6c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3350de8. Configure here.
- ce-work: integration lock paths anchor to the root the run actually lives under (via run_dir), so a fallback-root run validates and releases the lock it recorded after a sandbox/unsandboxed switch - ce-work: resume discovery scans other candidate roots read-only instead of repairing them, so an unwritable leftover /tmp tree cannot abort discovery before the writable root is scanned - test: lock path anchors to the fallback run's root Claude-Session: https://claude.ai/code/session_014wod7UqLtRtt91osnKtydm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed6c0c8f5d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- ce-ideate: prose said scratch is "not $TMPDIR" right above the block that selects the $TMPDIR fallback; describe /tmp as preferred and $TMPDIR as the validated fallback Claude-Session: https://claude.ai/code/session_014wod7UqLtRtt91osnKtydm
* fix(ce-commit-push-pr): root PR stacks on the parent PR the user named (EveryInc#1365) * fix(ce-babysit-pr): decode gh output as UTF-8 on Windows (EveryInc#1368) * fix(ce-prototype): cover decisions settled by seeing, not just driving (EveryInc#1369) * perf(tests): cut suite wall time by splitting the largest test file (EveryInc#1370) * fix(tests): stop the cross-model routes test reading the working tree (EveryInc#1371) * fix(ce-doc-review): ask only where a real choice exists, batch the rest (EveryInc#1373) * chore(orca): re-pin upstream provenance baseline to 421a337 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ce-doc-review): align Orca ownership wording with Apply routing Co-authored-by: Cursor <cursoragent@cursor.com> * feat(ce-prototype): add a seeing-mode craft floor and durable storage (EveryInc#1374) * fix(ce-pov): stop the panel guessing the cross-model host argument (EveryInc#1375) * chore(cross-model): pin the Grok peer to 4.6 (EveryInc#1376) * docs(skills): rewrite user skill pages for accuracy and clearer use (EveryInc#1377) * fix(commit): append known plan unit ids to commit subjects (EveryInc#1379) * fix(ce-work): stop sandboxed workers committing in linked worktrees (EveryInc#1382) * fix(ce-doc-review): edit HTML plans in native format (EveryInc#1381) * fix(ce-code-review): cover adversarial after quota or auth no-review (EveryInc#1380) * fix(skills): correct a rejected dispatch instead of spending the fallback (EveryInc#1383) * fix(ce-compound): find Claude sessions started outside the repo root (EveryInc#1378) * ci(windows-native): retry peer-job-runner smoke on ctypes flake (EveryInc#1384) * fix(ce-debug): stop asking at the handoff, stop shipping unoffered work (EveryInc#1385) * docs(solutions): record why skill gates state conditions, not git commands (EveryInc#1386) * fix(skills): drop the residual-findings record file for real sinks (EveryInc#1387) * fix(ce-doc-review): run the cross-model pass when CROSS_MODEL_PEERS is unset (EveryInc#1389) * fix(ce-proof): sync with current Proof v3 contract (EveryInc#1390) * fix(skill-authoring): make goal-first the default when authoring and reviewing skills (EveryInc#1391) * fix(cross-model): let reviews run on Fable and pin model/effort from CE config (EveryInc#1392) * docs(cross-model): point superseded peer benchmarks at the luna/xhigh decision (EveryInc#1393) * fix(cross-model): discover the Codex.app-bundled codex CLI and name the peer-CLI requirement (EveryInc#1395) * feat(cross-model): add cross_model_review_mode checkout egress gate (EveryInc#1396) * fix(ce-compound-refresh): compare knowledge-track learnings against guidance they name (EveryInc#1399) * docs(solutions): capture the named-guidance contradiction-check learning (EveryInc#1400) * fix(ce-compound): prefer the repo's own frontmatter vocabulary over the Rails-era enums (EveryInc#1394) * fix(ce-work): stop asking about branches before starting work (EveryInc#1397) * fix(review): answer covered cases on skill prose with the condition, not a patch (EveryInc#1401) * fix(scratch): fall back to $TMPDIR when /tmp cannot host the scratch root (EveryInc#1398) * feat(ce-skill-work): repo-local skill for authoring, editing, reviewing, and responding to review on skills (EveryInc#1402) * fix(ce-pov): reject non-final peer positions instead of folding them in (EveryInc#1403) * feat(manifest): add Agent Plugins v1.0.0 manifest support (EveryInc#1345) * chore: release main (EveryInc#1354) * fix(ce-work): run cross-model verification on warm checkouts (EveryInc#1404) * fix(orca): reconcile upstream skill contracts * fix(orca): preserve additive Codex session roots --------- Co-authored-by: Trevin Chow <trevin@trevinchow.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Summary
Skills that keep cross-invocation state (
ce-babysit-prresume, peer jobs,ce-workruns,ce-handoff,ce-compoundresearch artifacts) now work inside Claude Code's macOS sandbox. Before this, every one of them aborted at setup withOperation not permitted: the sandbox allowlists writes only under$TMPDIR(/tmp/claude-<uid>), and the durable root lived directly under/tmp. #1305 fixed the per-runmktempforms and deferred this durable root; this closes that gap.The root is now resolved the same way everywhere: prefer
/tmp/compound-engineering-<uid>, and fall back to${TMPDIR:-/tmp}/compound-engineering-<uid>only when the/tmproot cannot be created, is a symlink, is not owned by the user, or is not writable. Unsandboxed users keep the readable/tmppath and their existing state.Fixes #1294
Design decisions
/tmp, fall back to$TMPDIRrather than always deriving from$TMPDIR. Unsandboxed macOS$TMPDIRis/var/folders/.../T/, which is hostile to inspection, and switching would orphan existing state. A user's sandbox setting is normally stable across sessions, so discovery stays consistent. The accepted residual: someone alternating sandboxed and unsandboxed sessions resolves different roots, so state written in one mode is not found from the other.mkdir -p(a no-op) and evenchmod 700under the sandbox, then denies the first inner write.[ -w ]in shell andos.access(W_OK)in Python are denied by the sandbox for that root, so the fallback fires where a create-only probe would have reported success and died one line later.peer-job-runner.pycopies, andunit_workspace_state.pyuse the same candidates in the same order, so a job started from a skill's preamble is found by the runner'sstatus/wait/result.ce-handoffresume enumerates both candidate roots.Validation
sandbox-exec(Seatbelt) profile that allowlists writes only under aTMPDIRsubdirectory: the shippedce-compoundpreamble and the runner's default both resolved to the same$TMPDIR/compound-engineering-501root; the same commands still resolve/tmp/compound-engineering-501unsandboxed./tmpcandidate occupied by a file (all hosts) and with an existing 0500 root (POSIX, non-root), asserting the fallback path; Python fallback tests forjobs_root_base()andensure_root();ce-prototyperun-root test covers the fallback and the both-blocked fail-closed case.bun run test: 3117 pass;release:validateandplugin:validatepass.Security Disclosure
Path handling in the scratch-root preambles and Python defaults changed. The fallback root keeps the same symlink, ownership, and
0700checks as the primary; no new write location outside the user's own temp roots. One behavioral change: a symlinked or foreign-owned/tmp/compound-engineering-<uid>now silently redirects to the fallback instead of aborting withunsafe scratch root(safe direction; the fallback is still checked). No secrets, permissions, or dependency changes.Agent Disclosure
Claude Code · claude-fable-5