Sandbox friction log
Re-filed S504 (ruling R10), from runbooks/: a log, not a procedure.
Sandbox friction log
Section titled “Sandbox friction log”Single append-only log of commands that hit Claude Code sandbox friction. Maintained by
the /handoff skill (Step 2 item 6): at session close, append a row for any NEW friction
command not already listed. Durable fixes live in .claude/settings.json
(sandbox.excludedCommands, sandbox.filesystem.allowWrite,
sandbox.network.allowedDomains, sandbox.enableWeakerNetworkIsolation) — this log
records the history and the un-fixables so no session rediscovers them. start-session
does not read this file; it exists so /handoff can dedupe and so fixes have provenance.
Settings-key placement gotcha (root cause of the chronic gh failure): sandbox boolean
flags (enableWeakerNetworkIsolation, excludedCommands, …) sit directly under
sandbox, NOT under sandbox.network, and never as a dotted literal key — the
settings validator silently drops unknown keys.
| Command / class | Error signature | Status / fix | First seen |
|---|---|---|---|
gh / gh-axi (any Go-binary HTTPS) | tls: x509: OSStatus -26276 (Go can’t reach trustd) | FIXED 2026-07-15: sandbox.enableWeakerNetworkIsolation: true (was a dead misplaced key inside network since ~S440). Security note: opens trustd access — accepted trade. | ~S440 |
git push / pull / fetch (https) | x509 as above | Expected fixed by the same flag — VERIFY next session; if still failing it’s a different class, log it separately | ~S440 |
launchctl | write EPERM outside allowlist | FIXED 2026-07-15: sandbox.excludedCommands | ~S440 |
lavish-axi server launch | server inherits sandbox → 500s on session-open | FIXED 2026-07-15: sandbox.excludedCommands (never start it sandboxed) | S442 |
~/.mempalace writes (mempalace CLI: repair/mine/diary) | write EPERM | INTENTIONAL — do NOT blanket-allow (DR-009 single-writer posture, runbooks/mempalace-repair.md §3): reads work via mode=ro&immutable=1 with no allow; repairs run deliberately with sandbox disabled | ~S440 |
git worktree add | .claude/agents write-deny EPERM | bypass (dangerouslyDisableSandbox) — candidate for excludedCommands if it keeps recurring | S443 |
Top-level worktree create/remove (../<name>) | outside write allowlist | bypass | S447 |
Git ops that delete files (merge / reset / worktree remove) | Operation not permitted | bypass | S447 |
Worktree-husk rm -rf (nested .claude/agents) | EPERM (FR-006) | bypass | ~S440 |
supabase db push | direct DB connection blocked (non-HTTP) | bypass. gen types works sandboxed with DO_NOT_TRACK=1 (telemetry write crash otherwise) | S444 |
Shell redirect into supabase/types/database.types.ts | write denied | workaround: generate to scratchpad, then cp into place (works sandboxed) | S444 |
| ledger-cli WRITE path | patch-server health check EPERM (reads fine) | bypass — root cause not yet diagnosed (allowLocalBinding is true, so not port-binding; investigate) | S470 |
next build (Turbopack) | documented sandbox failure | bypass | pre-S440 |
| cmux CLI | stale socket path in allowlist | FIXED ~S443+: allowUnixSockets includes ~/.local/state/cmux/cmux-501.sock | S443 |
git cherry-pick/commit touching .claude/skills/** | Operation not permitted (unlink; sandbox deny on .claude/skills) | bypass (dangerouslyDisableSandbox) — hit when cherry-picking session-driver-cmux script changes (id-150) onto main | S473 |
git push (docs-site, HTTPS) | unable to get credential storage lock in 1000 ms: Operation not permitted | BENIGN — the ref update SUCCEEDS; only the credential-helper cache write is blocked. No fix needed (an SSH remote would silence it) | S473 |
bun run knip (in-sandbox) | read-deny on supabase/types/database.types.ts + lib/mcp/plugin-bundle.ts → FALSE counts (files +1, unresolved +156) while the baseline gate reads clean numbers outside | bypass (dangerouslyDisableSandbox) — knip must run unsandboxed or its counts are wrong; the baseline checker inherits the same requirement | S486 |
git add/commit/tag/stash-drop in sibling task-view repo (git -C /Users/.../task-view …) | Operation not permitted (index.lock create; task-view path outside allowWrite) | bypass (dangerouslyDisableSandbox) — recurring for any sibling-repo lane dispatch | S475 |
bunx tsc --noEmit | TS5033 tsbuildinfo write EPERM (spurious “error”) | bypass (dangerouslyDisableSandbox) or ignore the TS5033 line — real errors unaffected | S474 |
bun run test:e2e (Playwright webServer boot) | Turbopack “Symlink [project]/node_modules is invalid” — same class as the next build entry | bypass (dangerouslyDisableSandbox) | S474 |
gh run view --log-failed (CI failure triage) | failed to get run log: creating cache entry: open ~/.cache/gh/run-log-*.zip: operation not permitted — gh caches the run-log zip | bypass (dangerouslyDisableSandbox); durable fix would be sandbox.filesystem.allowWrite on ~/.cache/gh. gh run list/gh pr checks/gh run view --json are unaffected (no cache write) | S476 |
git merge origin/main touching .claude/{skills,agents}/** | error: unable to unlink old '.claude/…': Operation not permitted (Merge with strategy ort failed) — same .claude sandbox-deny class as the S473 cherry-pick entry, but a merge leaves untracked residue that must be git cleaned before retry | bypass (dangerouslyDisableSandbox) | S476 |
git worktree remove on canonical/.claude/worktrees/* | Operation not permitted (dir deletion outside allowWrite) | bypass (dangerouslyDisableSandbox) — hit pruning 16 landed S474 worktrees | S478 |
| ledger-cli WRITE path (refinement of the S470 entry) | Ledger server failed to become healthy within 10000ms deadline (inv 54) … no fallback to ungated write path | bypass — the sandbox blocks the spawned patch-server’s port-file/cache writes, so health never turns | S478 |
executor git commit (heredoc message) in agent worktrees | EPERM + stray shell text corrupting the commit message | bypass + file-based --amend (-F <msgfile>); recurring in Lane B2/D executors | S478 |
-
S477
git tag/git pushin the task-view sibling checkout —unable to create temporary file: Operation not permitted(.gitoutside write allowlist) — bypass (sandbox override per sibling-lane convention); push additionally needs the one-off HTTPS+token URL (no SSH key). -
S477 bare
mktemp -d—Operation not permitted(used/var/foldersdefault instead of$TMPDIR) — fix: ALWAYSmktemp -d "$TMPDIR/XXXX"; the silent empty-var fallout made--ledger-dir ""hit the REAL ledger (9990 incident). -
S477
git worktree remove—failed to delete .git/worktrees/...: Operation not permitted— bypass (override); worktree metadata dir is deny-listed. | leaf-executor git/prettier ops incanonical/.claude/worktrees/agent-*(dispatched from the procurement checkout) | EPERM on commit/format/rm — Agent-tool worktrees resolve under the canonical root, outside the procurement session’s write-allowlist | bypass per exec; durable fix = extendsandbox.filesystem.allowWritewith the canonical worktrees glob | S479 | | cmux worker writing OQ/final_report to the main-repo$KH_CMUX_EVENTS_DIRfrom a worktree cwd | EPERM (path outside worker sandbox allowlist); relative.claude/cmux-eventssilently writes the worktree copy instead (invisible to parent) | bypass + brief workers with the ABSOLUTE main-repo path; durable fix = allowlist$KH_CMUX_EVENTS_DIRin worker launch env | S479 | | EVERY sandboxed Bash spawn (S481, whole session) |E2BIGat spawn — “command line plus environment exceed the OS exec argument limit”: the sandbox profile embeds a deny path per registered git worktree (153 at session start) and the frozen profile never shrinks mid-session | bypass all session (dangerouslyDisableSandboxper call); durable fix = sweep landedcanonical/.claude/worktrees/agent-*at session close (S481 did: 48→2 kept) so the NEXT session’s profile builds lean; restart Claude Code rebuilds the profile | S481 | |git merge --ff-only origin/maintouchinghooks/**(main checkout) |error: unable to unlink old 'hooks/…': Operation not permitted— and the failed FF leaves the working tree HALF-UPDATED (files from the target commit written, HEAD not moved), so a naive retry then fails “local changes would be overwritten” | bypass + recover withgit reset --hard origin/main(safe only after confirming zero local-only commits/work) | S483 | |bun install|error: bun is unable to access tempdir: PermissionDenied| bypass (dangerouslyDisableSandbox) | S483 | |gh run view --job <id> --log(sandboxed) |failed to get run log: creating cache entry: open ~/.cache/gh/run-log-*.zip: operation not permitted— gh writes its log cache outside the allowlist | bypass; durable fix candidate = add~/.cache/ghto sandbox.filesystem.allowWrite | S484 | |ssh root@87.106.72.59(Server B / VPS ops — deploy-proof loopback probes, docker exec) | network deny (host not insandbox.network.allowedHosts) | bypass per call; durable fix candidate = add87.106.72.59tosandbox.network.allowedHosts| S485 | |git cherry-pickcreating NEW files underhooks/(main checkout) |unable to create file hooks/okf/…: Operation not permitted— the.claude/hooks-adjacent deny collides with the repo’s Reacthooks/dir (same class as the S483 merge entry, cherry-pick variant); the failed pick leaves BOTH modified tracked files AND untracked created files, so retry needsgit reset --hard+git clean -fd <paths>; concurrent agent worktrees sharing.gitcan also add a transientindex.lockfailure on the retry | bypass + two-step recovery above | S488 | -
S489 (Intent clone
/Users/liamj/intent/workspaces/okf-system/canonical, provisioning class) — fresh Intent worktree shipped WITHOUT.env.local(added mid-session); bothledger-cliand the GLM-5.2 OpenRouter producer need it — fix: copy.env.localinto the clone before any ledger/producer work. -
S489 —
.cache/task-view-*vendored dir neededbun installbefore ledger-cli mutations worked (missing deps → mutation failure) — fix:bun installin the clone before first ledger write. -
S489 —
.claude/skills/session-driver-cmux/scriptsabsent in the Intent clone → 177oq/*tests fail environmentally (not a real regression) — fix: expect environmental failures, or hydrate the scripts dir before running those suites. -
S489 — the Python OKF producer does NOT auto-read
.env.local(unlike bun, which auto-loads it) — env must be loaded explicitly (e.g.set -a; source .env.local; set +a) before invoking the producer. -
S489 (harness watchdog, not sandbox) — long quiet
pytestruns trip the stream watchdog and get killed — fix: run gates FOREGROUND + streamed (PYTHONUNBUFFERED=1, pipe totail) so output keeps the stream alive. -
S489 —
ledger-climutations confirmed to require sandbox DISABLED (docs-site write + spawns a ledger server) — same class as the S470/S478 ledger-cli WRITE entries; bun auto-loads.env.localfor these. -
S490 — in-sandbox
git fetch/push(canonical workspace): “fatal: unable to get credential storage lock in 1000 ms: Operation not permitted” — bypass (run git network ops with sandbox disabled). -
S490 — in-sandbox access to out-of-repo paths (e.g.
git -C $KH_PRIVATE_DOCS_DIR ...): blocks indefinitely instead of clean EPERM (agent hangs to turn ceiling) — durable fix applied: sandbox.filesystem.disabled=true in .claude/settings.json (landed 03ad0ae1).