#1545·cc-connect

[Bug] run_as_user: default data_dir under /root (0700) is untraversable by the agent — system-prompt file EACCES persists despite #1433

Author: vuyivCreated Jul 14, 2026Updated Sep 14, 2026
Labelsstale

cc-connect Version

main / v1.5.0-beta.1 (reproduced on a build rebased onto origin/main that already includes #1433). Structural — the default data_dir resolution is unchanged since run_as_user landed (#560), so 1.3.x–1.5.x are all affected.

Operating System

Linux (Docker container; reproduced on Alpine, but /root is 0700 on Debian/Ubuntu too)

Agent Type

Claude Code (claudecode)

Platform

Any platform that appends a system prompt (Teams/Slack/Weixin/MAX inject a formatting prompt, so the per-spawn append path fires on every message)

Bug Description

With run_as_user set, the spawned agent still fails to read its system-prompt file even after #1433, when data_dir is left at the default:

Error reading append system prompt file: EACCES: permission denied, open '/root/.cc-connect/agent-prompts/cc-connect-system-3746196141.md'

The root cause is the default data_dir location, not the file mode. #1433 correctly made the per-spawn file world-readable (agent/claudecode/session.go:156, f.Chmod(0o644)), and both prompt-writing paths create their own directories 0755 (ensureSharedSystemPromptFileos.MkdirAll(dir, 0o755) at session.go:114; writeTempAppendPromptFileos.MkdirAll(dir, 0o755) at session.go:137). So the file and the agent-prompts/ dir are both agent-readable.

But cc-connect resolves data_dir to $HOME/.cc-connect (resolveDataDir, cmd/cc-connect/sessions.go:141-148; documented default ~/.cc-connect, sessions.go:433). cc-connect runs as root, so HOME=/root and data_dir=/root/.cc-connect. /root itself is mode 0700 on standard Linux — the unprivileged run_as_user agent has no execute/traverse bit on that ancestor, so it cannot reach the (world-readable) file underneath. The EACCES is on path traversal of /root, not on the file's own mode. MkdirAll(…, 0o755) on the .cc-connect subdirs can't help, because the blocker is the 0700 /root ancestor that cc-connect neither creates nor adjusts.

This is the directory-traversal counterpart to #1429: #1433 fixed the file's own permissions but not the directory the run_as_user agent must traverse to reach it. It is the same class as #1527 (a correctly-resolved target still blocked by root-owned restrictive perms under run_as_user) — there the socket is 0600, here the ancestor dir is 0700.

Steps to Reproduce

  1. Configure a claudecode project with run_as_user set (uid ≠ 0), cc-connect itself running as root, and data_dir left at the default. Concrete config that reproduces it:
toml
# data_dir omitted → defaults to ~/.cc-connect → /root/.cc-connect (root's 0700 home)

[[projects]]
run_as_user = "claude"                                    # target user: claude, uid=1001 gid=1001(claude)
run_as_env  = ["CLAUDE_CODE_OAUTH_TOKEN", "CC_SESSION_KEY"]

[projects.agent]
type = "claudecode"
  1. Use a platform that appends a system prompt (e.g. Teams/Slack), or set append_system_prompt.
  2. Send a message → the agent fails to start with this verbatim error:
Error reading append system prompt file: EACCES: permission denied, open '/root/.cc-connect/agent-prompts/cc-connect-system-<n>.md'
  1. Confirm the traversal blocker is the /root ancestor, not the file (as the target user):
$ sudo -u someuser ls -ld /root/.cc-connect
ls: cannot access '/root/.cc-connect': Permission denied
$ ls -ld /root
drwx------  root root  /root            # 0700 — no traverse for others
$ ls -l /root/.cc-connect/agent-prompts/  # (as root) file itself is world-readable
-rw-r--r--  root root  cc-connect-system-<n>.md   # 0644 per #1433
  1. Workaround: point data_dir at a path the target user can traverse (e.g. /home/someuser/.cc-connect, mode 0755); the same message then succeeds.

Expected Behavior

When run_as_user is set, the agent (running as that user) should be able to read the system-prompt file cc-connect writes for it. #1433 made the file world-readable; the data_dir path it lives under must also be traversable by the target user. Options, roughly in order of preference:

  • (a) When run_as_user is set, ensure the resolved data_dir is traversable by the target user — e.g. chmod o+x (or chown) the data_dir chain cc-connect controls, mirroring how #1433 made the file agent-readable.
  • (b) Don't default data_dir under root's 0700 home when run_as_user differs from the daemon user — default to a shared, traversable location (e.g. /var/lib/cc-connect) or the target user's home.
  • (c) At minimum, fail loudly at startup with a clear message when the resolved data_dir is not traversable by run_as_user, instead of surfacing a per-message EACCES the operator has to reverse-engineer.

Related

  • #1429 / #1433 — sibling run_as_user EACCES on the per-spawn system-prompt file's own mode. Fixed the file (06000644), but not the directory the agent must traverse to reach it — this issue.
  • #1527 — run_as_user EACCES on the 0600 root-owned API control socket. Permission-side counterpart on a different file under the same data_dir.
  • #966 — data_dir / socket-path resolution under a custom data_dir (path-side; distinct from this traversal-permission side).

90% Claude