[Bug] run_as_user: default data_dir under /root (0700) is untraversable by the agent — system-prompt file EACCES persists despite #1433
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 (ensureSharedSystemPromptFile → os.MkdirAll(dir, 0o755) at session.go:114; writeTempAppendPromptFile → os.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
- Configure a claudecode project with
run_as_userset (uid ≠ 0), cc-connect itself running as root, anddata_dirleft at the default. Concrete config that reproduces it:
# 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"- Use a platform that appends a system prompt (e.g. Teams/Slack), or set
append_system_prompt. - 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'- Confirm the traversal blocker is the
/rootancestor, 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- Workaround: point
data_dirat a path the target user can traverse (e.g./home/someuser/.cc-connect, mode0755); 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_useris set, ensure the resolveddata_diris traversable by the target user — e.g.chmod o+x(or chown) thedata_dirchain cc-connect controls, mirroring how #1433 made the file agent-readable. - (b) Don't default
data_dirunder root's0700home whenrun_as_userdiffers 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_diris not traversable byrun_as_user, instead of surfacing a per-messageEACCESthe operator has to reverse-engineer.
Related
- #1429 / #1433 — sibling
run_as_userEACCES on the per-spawn system-prompt file's own mode. Fixed the file (0600→0644), but not the directory the agent must traverse to reach it — this issue. - #1527 —
run_as_userEACCES on the0600root-owned API control socket. Permission-side counterpart on a different file under the samedata_dir. - #966 —
data_dir/ socket-path resolution under a customdata_dir(path-side; distinct from this traversal-permission side).
90% Claude
Source: chenhg5/cc-connect