In-process filesystem sandbox restriction applied mid-session to running headless sessions; irreversible without process restart; account-scoped synchronized onset
Summary
At 09:03:46–09:03:57 KST on 2026-07-21, three concurrent, long-running, headless Claude Code sessions on one account began raising EPERM on open()/write()/xattr operations against a single local directory tree they had been writing to continuously for hours/days, while ls/stat on the same paths kept succeeding, and while two other concurrent headless sessions on a second account (writing to the same files, checked at the same minute) were completely unaffected. We believe an in-process, self-applied Seatbelt-style filesystem sandbox was switched on mid-session via a server-delivered flag, and that it could not be reverted by a live settings change — only by killing and restarting the process. Filing to ask whether this is a known/intended rollout and whether mid-session in-process sandbox application is expected for long-running sessions.
Environment
- macOS 15.6.1 (Darwin 24.6.0, build 24G90)
- Claude Code 2.1.216 on most sessions. The binary had auto-updated 2.1.215 → 2.1.216 earlier the same morning (update log timestamp
2026-07-20T22:41:47ZUTC, i.e. ~07:41 KST), roughly 1h20m before the incident. - One of the three affected sessions was still running the pre-2.1.216 binary (2.1.215) at the moment of onset — it had not yet auto-updated.
- All 5 concurrently running sessions were headless (launched non-interactively via an internal supervisor, no attached TTY), long-running, with uptimes at onset ranging from ~90 minutes to ~6 days across the fleet.
- Two Claude accounts in concurrent use, isolated via separate
CLAUDE_CONFIG_DIR: account 1 (default config dir) ran 2 of the 5 sessions; account 2 (a distinctCLAUDE_CONFIG_DIR) ran the other 3. - All 3 affected sessions were on account 2. Both account-1 sessions were unaffected throughout.
Symptom signature
- Onset was mid-session, not at boot: a debug/log file each session had been appending to continuously since its own boot flatlined at 09:03 for all 3 account-2 sessions, within an 11-second window (09:03:46–09:03:57 KST).
- After onset, the affected processes got raw
EPERMonopen/write/xattragainst paths under one specific directory tree (~/Documents/<data dir>, the sessions' own persistent-data directory) that they had write access to and had been actively writing to seconds before. - Directory listing /
staton the exact same paths continued to succeed from the affected process throughout — this was a file-data operation failure, not a file-existence/metadata failure. - Network and stdio for the affected sessions stayed alive; these were not crashes or disconnects, just writes silently failing.
- The 09:03:46–09:03:57 synchronization held across 3 sessions with wildly different uptimes (90 min vs 6 days) — ruling out any per-session-age or per-session-timer trigger (e.g., a token TTL local to that session).
- Per-account axis: at the exact same minute, on the exact same files, a process on account 1 could read/write successfully while a process on account 2 got
EPERM. This was checked directly (same file, same wall-clock minute, different account, different result). - One affected session was still on the pre-2.1.216 binary and was hit identically to the two sessions already on 2.1.216 — ruling out the 07:41 KST binary update as the cause.
Diagnostics that ruled things out
- Unix permissions and extended attributes on the affected directory tree: unchanged, verified clean.
- macOS TCC (privacy/consent) logs: silent — no TCC denial entries correlated with the affected processes or paths.
- Unified system/kernel log: no sandbox-deny or related denial messages found correlated with onset.
softwareupdatehistory: no OS-level update at/near 09:03 KST.- The affected account's
CLAUDE_CONFIG_DIRitself: nothing inside it changed at 09:03. The first modification to any file in that config dir occurred at 09:08, ~5 minutes after onset (and was our own diagnostic/mitigation activity, not a pre-existing trigger). - The affected directory is not an iCloud-synced location —
realpathresolves to a local path, not into aMobile Documents/CloudDocs target — ruling out iCloud eviction/placeholder-file behavior.
Why we believe it's an in-process, self-applied Seatbelt sandbox via staged rollout
- The
ls/stat-alive vsopen/write-dead split is the textbook signature of a macOS Seatbelt (sandbox_init) profile denyingfile-read-data/file-write-datawhile still permittingfile-read-metadata— not a TCC signature, which denies at a different granularity and normally logs distinctly (and here, TCC logged nothing). - We hot-patched the live
settings.jsonfor the affected account's config dir withsandbox.enabled: false,filesystem.disabled: true, plus explicitallowRead/allowWritefor the affected directory. This did not recover the already-running affected sessions — re-tested at 09:34 KST, still identicalEPERMon the same paths. We take this as evidence the restriction was applied via an in-process mechanism (e.g. asandbox_init()-class call made once, mid-session) that a later config read cannot undo for that already-running process — i.e. irreversible in-process. - Killing and relaunching the affected processes ("fresh boot"), with
sandbox.enabled: false/filesystem.disabled: truealready present in the config at process start, did recover write access. - The account-scoped, sub-minute-synchronized onset across sessions of very different uptimes looks like a server-delivered feature-flag/staged-rollout event picked up mid-session (poll or push) and then applied in-process, rather than anything client-local.
- Circumstantial support: the local feature-flag cache file under the affected account's config dir was being rewritten on roughly a per-minute cadence throughout the day of the incident, while the equivalent flag cache under the unaffected account's config dir had not been touched in weeks. Consistent with the affected account being enrolled in an active staged-rollout/refresh cycle the other account was not part of.
What we could NOT pin (explicitly unconfirmed)
- Exact flag identity. Diffing the flag cache before/after onset did not surface any entry with an obviously sandbox-related name — the flags are opaque/codenamed, so we have no textual confirmation of which flag (if any) flipped.
- Recovery mechanism ambiguity. We cannot distinguish "our
settings.jsonpatch is what made fresh boots recover" from "any fresh boot would have recovered regardless of our patch, e.g. because the server-side flag had already been rolled back by then." We did not test a fresh boot without the patch during the window it would have mattered. - Trigger hypothesis, unverified. We noticed OAuth token rotation/refresh on the affected account around the same window and think it's plausible as a flag-delivery trigger, but this is a hunch, not a confirmed causal link. Note that auth itself did not appear to be the blocking mechanism directly —
lskept succeeding and TCC logged nothing, so if OAuth rotation is involved it would be as a trigger for flag delivery, not as an access-control block in itself.
Ask
- Was there a staged rollout of sandbox/filesystem-restriction enforcement around 2026-07-21 00:03 UTC (09:03 KST) that could apply differentially per-account/org?
- Is it intended behavior for a sandbox/Seatbelt restriction to be applied to an already-running session's own process (not just future child-process spawns) via an in-process mechanism triggered mid-session — such that (a) it cannot be reverted by a subsequent
settings.jsonchange without a full process restart, and (b) it can affect the main process's own established working directories? - For long-running headless/non-interactive sessions specifically, is there a way to (a) avoid mid-session in-process sandbox flag application, or (b) at minimum detect an incoming sandbox-flag change before it lands, so a session can drain/checkpoint before its own writes start failing?
Impact
Approximately 41 minutes of write loss (09:03–09:44 KST) across the 3 affected sessions before full recovery. Loss was confined to session-local logs/state/caches for that window; durable records outside the affected directory tree were unaffected because writes were manually rerouted to an alternate location during the incident. Recovery for two of the three sessions was via external process restart with the corrected config in place before boot; the third additionally needed a manual kill due to an unrelated gap in our own restart automation (not a Claude Code issue, noted only for completeness).
Recurrence watch
No recurrence in a ~2h40m observation window under mitigation (recovery completed 09:44 KST; last checked 12:24 KST, same day):
- All three previously-affected sessions' writes into the affected directory tree resumed at recovery and have been continuous since — the canary log that flatlined at 09:03 has grown through 12:23 KST, and the sessions' own transcript files show updates through the check time.
- No new
EPERMoccurrences in any session's debug output after mitigation (post-recovery matches for the string were all quotations of our own incident notes being echoed into context, not errors). - The mitigation
settings.json(withsandbox.enabled: false/filesystem.disabled: true/ explicit allowRead+allowWrite) has been untouched since 09:35 KST and remains in place.
Caveat: this is "no re-trigger under mitigation." We have not re-tested an unmitigated fresh boot on the affected account, so we cannot say whether the server-side condition has cleared — only that the mitigated configuration has held for the window. We are keeping the mitigation keys in place until we understand the rollout behavior.