Skip to content

fix(scratch): fall back to $TMPDIR when /tmp cannot host the scratch root - #1398

Merged
tmchow merged 5 commits into
mainfrom
tmchow/debug-issue-1294
Aug 16, 2026
Merged

fix(scratch): fall back to $TMPDIR when /tmp cannot host the scratch root#1398
tmchow merged 5 commits into
mainfrom
tmchow/debug-issue-1294

Conversation

@tmchow

@tmchow tmchow commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Skills that keep cross-invocation state (ce-babysit-pr resume, peer jobs, ce-work runs, ce-handoff, ce-compound research artifacts) now work inside Claude Code's macOS sandbox. Before this, every one of them aborted at setup with Operation not permitted: the sandbox allowlists writes only under $TMPDIR (/tmp/claude-<uid>), and the durable root lived directly under /tmp. #1305 fixed the per-run mktemp forms 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 /tmp root cannot be created, is a symlink, is not owned by the user, or is not writable. Unsandboxed users keep the readable /tmp path and their existing state.

Fixes #1294

Design decisions

  • Prefer /tmp, fall back to $TMPDIR rather than always deriving from $TMPDIR. Unsandboxed macOS $TMPDIR is /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.
  • Probe writability, not just creation. A root created earlier by an unsandboxed session passes mkdir -p (a no-op) and even chmod 700 under the sandbox, then denies the first inner write. [ -w ] in shell and os.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.
  • One order in shell and Python. All 17 shell preambles, the six byte-identical peer-job-runner.py copies, and unit_workspace_state.py use the same candidates in the same order, so a job started from a skill's preamble is found by the runner's status/wait/result. ce-handoff resume enumerates both candidate roots.

Validation

  • Reproduced with a sandbox-exec (Seatbelt) profile that allowlists writes only under a TMPDIR subdirectory: the shipped ce-compound preamble and the runner's default both resolved to the same $TMPDIR/compound-engineering-501 root; the same commands still resolve /tmp/compound-engineering-501 unsandboxed.
  • New execution tests run every shipped preamble with the /tmp candidate occupied by a file (all hosts) and with an existing 0500 root (POSIX, non-root), asserting the fallback path; Python fallback tests for jobs_root_base() and ensure_root(); ce-prototype run-root test covers the fallback and the both-blocked fail-closed case.
  • bun run test: 3117 pass; release:validate and plugin:validate pass.

Security Disclosure

Path handling in the scratch-root preambles and Python defaults changed. The fallback root keeps the same symlink, ownership, and 0700 checks 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 with unsafe scratch root (safe direction; the fallback is still checked). No secrets, permissions, or dependency changes.

Agent Disclosure

  • Model: Claude Code · claude-fable-5

Compound Engineering

…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
@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown

PR Summary

Cursor Bugbot is generating a summary for commit a2c9866. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread skills/ce-work/scripts/peer-job-runner.py Outdated
- 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
Comment thread skills/ce-work/scripts/unit_workspace_state.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread skills/ce-work/scripts/unit_workspace_state.py
- 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread skills/ce-work/scripts/unit_workspace_state.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread skills/ce-work/scripts/unit_workspace_state.py
Comment thread skills/ce-work/scripts/unit_workspace_lifecycle.py
- 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread skills/ce-babysit-pr/SKILL.md
Comment thread skills/ce-work/scripts/unit_workspace_state.py
Comment thread skills/ce-ideate/SKILL.md
- 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
@tmchow
tmchow merged commit 945de22 into main Aug 16, 2026
5 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 16, 2026
ethras added a commit to ethras/compound-engineering-orca that referenced this pull request Aug 16, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant