Claude.app: CCD subprocess fails with EACCES on shared /tmp/claude-settings-*.json across macOS users
Claude Code: /tmp/claude-settings-*.json collision across macOS users
Summary
When two macOS user accounts (fast user switching) are simultaneously running Claude.app, the Claude Code CCD subprocess fails to start for the second user with exit code 1 and no stdout. The UI reports Claude Code process exited with code 1 / hadFirstResponse=false / reason=no_response immediately after sending any message.
Root cause: Claude Code caches the parsed --settings argument in /tmp/claude-settings-<hash>.json. The hash is derived only from the --settings input, not the uid. When both Claude.app instances pass the same --settings value (both pass --settings {} by default), they collide on the same path. Because /tmp has the macOS sticky bit, whichever user created the file first owns it and the other user cannot overwrite or unlink it. The subprocess exits before emitting any output.
Environment
- macOS 26.4.1 (25E253), Apple Silicon (Mac17,8)
- Claude.app 1.3883.0
- Bundled Claude Code 2.1.111 at
/Users/<user>/Library/Application Support/Claude/claude-code/2.1.111/claude.app/Contents/MacOS/claude - Two macOS user accounts logged in via fast user switching, each running Claude.app
Observed behavior
In Claude.app for the second user:
- Every new conversation immediately shows
Claude Code process exited with code 1 - No assistant response renders (
hadFirstResponse=false,reason=no_response) ~/Library/Logs/Claude/main.logshows repeated:
````
[error] Session <id> query error: Claude Code process exited with code 1
at sIr.getProcessExitError (.vite/build/index.js:390:8041)
at ChildProcess.i (.vite/build/index.js:390:11086)
[info] [CCD CycleHealth] unhealthy cycle for <id> (0s, hadFirstResponse=false, reason=no_response)
- Cowork VM sessions (different spawn path:
Using Claude VM spawn function) are unaffected. - The bundled binary works fine when invoked standalone from a terminal.
Reproduction
Two macOS users logged in simultaneously (fast user switching). Each user launches Claude.app and tries to start a new conversation.
Minimal reproduction of the underlying failure, using the exact spawn args Claude.app uses (captured via ps -A -o user,pid,ppid,command):
BUNDLE="$HOME/Library/Application Support/Claude/claude-code/2.1.111/claude.app/Contents/MacOS/claude"
SP="$HOME/Library/Application Support/Claude/local-agent-mode-sessions/skills-plugin/<org-uuid>/<session-uuid>"
printf '%s\n' '{"type":"user","message":{"role":"user","content":"hi"}}' | \
"$BUNDLE" \
--output-format stream-json --verbose --input-format stream-json \
--effort xhigh --model 'claude-opus-4-7[1m]' \
--permission-prompt-tool stdio \
--allowedTools 'mcp__computer-use,mcp__ccd_session__spawn_task,mcp__ccd_session__mark_chapter' \
--setting-sources=user,project,local \
--permission-mode acceptEdits \
--allow-dangerously-skip-permissions \
--include-partial-messages \
--plugin-dir "$SP" \
--replay-user-messages \
--settings '{}'
; echo "EXIT=$?"
Output when the other user already owns the cache file:
EXIT=1
stdout: (empty)
stderr: Error processing settings: EACCES: permission denied, open '/tmp/claude-settings-44136fa355b3678a.json'
State of the blocking file:
$ ls -la /tmp/claude-settings-44136fa355b3678a.json
-rw-r--r-- 1 Haven wheel 2 Apr 23 14:18 /tmp/claude-settings-44136fa355b3678a.json
$ cat /tmp/claude-settings-44136fa355b3678a.json
{}
$ ls -ld /tmp
lrwxr-xr-x 1 root wheel 11 /tmp -> private/tmp # sticky bit on /private/tmp
The hash 44136fa355b3678a is deterministic for --settings {}, so every Claude.app spawn with default settings collides on the same file regardless of user.
Why the spawn ends up with this arg
Claude.app always passes --settings '{}' to the bundled binary (confirmed in the ps dump of the live process tree):
/Applications/Claude.app/Contents/Helpers/disclaimer \
/Users/Haven/Library/Application Support/Claude/claude-code/2.1.111/claude.app/Contents/MacOS/claude \
--output-format stream-json --verbose --input-format stream-json \
--effort xhigh --model claude-opus-4-7[1m] \
--permission-prompt-tool stdio \
--allowedTools mcp__computer-use,mcp__ccd_session__spawn_task,mcp__ccd_session__mark_chapter \
--setting-sources=user,project,local \
--permission-mode acceptEdits --allow-dangerously-skip-permissions \
--include-partial-messages \
--plugin-dir /Users/Haven/Library/Application Support/Claude/local-agent-mode-sessions/skills-plugin/<org>/<sess> \
--replay-user-messages \
--settings {}
So the collision affects every new Claude.app conversation for the second user, not just edge cases.
Expected behavior
Any of the following would fix it:
- Namespace the cache path by uid, e.g.
/tmp/claude-settings-<uid>-<hash>.json. - Use
$TMPDIRinstead of/tmp— on macOS this is already per-user (/var/folders/.../T/), which is the correct location for per-user scratch files. - If the target file exists and is not owned by the current user, fall through to a uid-suffixed fallback path instead of failing.
- At minimum, surface the EACCES error to the Claude.app UI instead of collapsing it into a generic "process exited with code 1".
$TMPDIR (option 2) is the standard macOS behavior and is likely the smallest correct change.
Workarounds
- Only run Claude.app in one macOS user at a time.
- If both users are active, the blocked user can run
sudo rm /tmp/claude-settings-*.jsonand relaunch Claude.app — but the file will be recreated by the other user's Claude.app as soon as it spawns a CCD subprocess, so this recurs.
Signals that narrow the search for anyone hitting this
- Exit code 1 with zero stdout,
hadFirstResponse=false,reason=no_response. - Standalone invocation of the bundled binary works; Claude.app invocation does not.
- Cowork VM sessions work (they use a different spawn code path).
- Another macOS user is logged in concurrently (
dscl . -list /Usersshows more than one non-system user, andps -A -o usershows Claude processes under a uid other than yours). ls -la /tmp/claude-settings-*.jsonshows files owned by a different user.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗