Desktop SSH: ccd-cli /tmp/claude-settings-{hash}.json permission collision in multi-user hosts causes exit 1

Status Closed — duplicate
Reported on v2.1.119
Maintainer reply None cached
Activity 3 comments · opened Apr 26, 2026 · closed Apr 29, 2026

Summary

When multiple Linux users on the same host connect to that host via Claude Desktop's SSH workspace feature, the second user's ccd-cli daemon exits with code 1 on every initialize. The Desktop UI shows Claude Code process exited with code 1. The first user works fine.

Root cause: ccd-cli writes its settings cache to /tmp/claude-settings-{sha256(settings)[:16]}.json. When two users receive identical --managed-settings/--settings from Desktop (the common case — both {}), the SHA256 hash is identical, so they collide on the same file in the world-shared /tmp/ directory.

Environment

  • ccd-cli: 2.1.119 (deployed by Desktop, not user-installed)
  • Claude Desktop: 1.4758.0 (macOS)
  • Remote host OS: Ubuntu 22.04.5 LTS, x86_64
  • Remote claude CLI: 2.1.119 (native installer at ~/.local/share/claude/versions/)
  • Both users authenticated via Claude Max plan, separate accounts

Steps to reproduce

  1. Have a Linux host with two non-root user accounts (userA, userB), neither in the other's primary group.
  2. From Claude Desktop, set up two SSH connections — one to userA@host, one to userB@host.
  3. Connect as userA, open any project, send a message → works fine. (/tmp/claude-settings-44136fa355b3678a.json gets created with mode 664, owned by userA.)
  4. Connect as userB, open any project, send a message → Claude Code process exited with code 1.

Evidence

The exact stderr from the failing ccd-cli (captured via wrapper)

Error processing settings: EACCES: permission denied, open '/tmp/claude-settings-44136fa355b3678a.json'

Hash is SHA256 of settings content, truncated to 16 hex chars

>>> import hashlib
>>> hashlib.sha256(b'{}').hexdigest()
'44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'
                                  ^^^^^^^^^^^^^^^^
                                  matches the filename in /tmp/

Both users' Desktop spawns ccd-cli with --managed-settings {} (and the second user additionally with --settings {}). Because the actual settings JSON is {} for both, the SHA256 prefix is identical → identical filename → guaranteed collision.

File permission state

$ ls -la /tmp/claude-settings-44136fa355b3678a.json
-rw-rw-r-- 1 userA userA 2 Apr 26 01:52 /tmp/claude-settings-44136fa355b3678a.json

userB is not in userA's group → no write permission. ccd-cli appears to open this file with O_RDWR (likely an atomic write/refresh pattern, even when the desired content is unchanged) → EACCES.

Captured initialize payload that triggers it

{
  "request_id": "ai5p9jybpt",
  "type": "control_request",
  "request": {
    "subtype": "initialize",
    "hooks": {
      "PreToolUse": [
        {"matcher": "Edit|Write|MultiEdit|NotebookEdit", "hookCallbackIds": ["hook_0"]},
        {"matcher": "mcp__ccd_directory__request_directory", "hookCallbackIds": ["hook_1"]},
        {"matcher": "mcp__.*", "hookCallbackIds": ["hook_2"]}
      ],
      "Stop": [{"hookCallbackIds": ["hook_3"]}]
    },
    "sdkMcpServers": ["Claude in Chrome", "mcp-registry", "ccd_session", "scheduled-tasks"]
  }
}

The exit happens regardless of hooks content (verified by feeding ccd-cli minimal initialize payloads with no hooks — also exit 1 if the cache file is unwritable).

Workaround (per-user)

Wrap ccd-cli with a script that redirects TMPDIR to a private directory:

#!/bin/bash
export TMPDIR="$HOME/.claude/tmp"
mkdir -p "$TMPDIR"
exec "$HOME/.claude/remote/ccd-cli/<version>.real" "\$@"

(Rename the real binary to .real and put the wrapper in its place.) This makes ccd-cli write the cache to ~/.claude/tmp/ instead of /tmp/, avoiding the collision permanently.

Caveat: Desktop may overwrite the wrapper on next deployment if it does a content/size check on the binary.

Suggested fix

Move the settings cache out of the world-shared /tmp/ and into a per-user location:

  • $XDG_CACHE_HOME/claude/ (defaulting to ~/.cache/claude/), or
  • ~/.claude/cache/, or
  • include getuid() in the filename: claude-settings-{uid}-{hash}.json

Any of these would eliminate the collision entirely without behavioral change for single-user setups.

Impact

Affects any team / lab environment where multiple developers share a single SSH host (cloud VM, dev server, on-prem workstation). The first user to connect "wins" the cache; everyone else is silently broken until the cache file is manually chmod 666'd or deleted.

Related

  • Initially misdiagnosed this as #50698 (hookCallbackId-related daemon exit) due to similar surface symptom, but the actual mechanism is filesystem permission, not callback protocol.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗