{"record":{"id":"ea77567a3938c310","repo":"chenhg5/cc-connect","slug":"claudecode-chmod-per-spawn-prompt-file-0o644-w","errorCode":null,"errorMessage":"claudecode: chmod per-spawn prompt file 0o644: %w","messagePattern":"claudecode: chmod per-spawn prompt file 0o644: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/session.go","lineNumber":159,"sourceCode":"\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif _, err := f.WriteString(content); err != nil {\n\t\t_ = f.Close()\n\t\t_ = os.Remove(f.Name())\n\t\treturn \"\", err\n\t}\n\t// os.CreateTemp defaults to mode 0600 owned by the cc-connect process\n\t// user (often root when launched by systemd). When the agent is spawned\n\t// under run_as_user, the target user is different and gets EACCES on\n\t// 0600 root-owned files (issue #1429). The shared prompt file already\n\t// uses 0o644 (see ensureSharedSystemPromptFile → writeFileAtomic);\n\t// the per-spawn temp file is just a superset of the shared content\n\t// and is equally non-secret, so we mirror that mode here.\n\tif err := f.Chmod(0o644); err != nil {\n\t\t_ = f.Close()\n\t\t_ = os.Remove(f.Name())\n\t\treturn \"\", fmt.Errorf(\"claudecode: chmod per-spawn prompt file 0o644: %w\", err)\n\t}\n\tif err := f.Close(); err != nil {\n\t\t_ = os.Remove(f.Name())\n\t\treturn \"\", err\n\t}\n\treturn f.Name(), nil\n}\n\n// writeFileAtomic writes data to path via a temp file + rename, so a\n// crash mid-write does not leave a half-written prompt file that the\n// next spawn would mistake for valid content.\nfunc writeFileAtomic(path string, data []byte, perm os.FileMode) error {\n\tdir := filepath.Dir(path)\n\tf, err := os.CreateTemp(dir, \".cc-connect-system-*.tmp\")\n\tif err != nil {\n\t\treturn err\n\t}\n\ttmp := f.Name()","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/session.go#L141-L177","documentation":"writeTempAppendPromptFile (agent/claudecode/session.go:159) creates a per-spawn temp file holding the append system prompt, and calls f.Chmod(0o644) so non-owner users (e.g. a run_as_user target) can read it — mirroring the shared prompt file's mode. If the Chmod syscall fails, it closes and removes the temp file and returns 'claudecode: chmod per-spawn prompt file 0o644: %w'.","triggerScenarios":"Calling newClaudeSession (which routes through writeTempAppendPromptFile when platformPrompt/appendSystemPrompt are non-empty) on a filesystem where f.Chmod fails — e.g. Windows where os.File.Chmod has limited semantics, or temp dirs on filesystems/ACLs that reject mode changes.","commonSituations":"Windows builds where Chmod on an open file fails or behaves oddly; temp directory mounted with restrictive umask/ACLs; running inside containers with certain volume mounts (some network filesystems reject chmod).","solutions":["Check the wrapped error to identify the failing filesystem; if on Windows, confirm this build supports the Chmod path used here.","Use a different temp directory (TMPDIR/os.TempDir override) on a local filesystem that supports chmod.","If using run_as_user, ensure TMPDIR is set to a world-traversable path like /tmp instead of a private user cache dir.","Upgrade to a version where the Windows chmod path is handled or skipped; the prompt content is non-secret so relaxed modes are safe."],"exampleFix":"// before\nos.Setenv(\"TMPDIR\", filepath.Join(homeDir, \".cache\", \"cc-connect\"))\npath, err := writeTempAppendPromptFile(ccDataDir, prompt) // chmod fails on restricted dir\n// after\nos.Setenv(\"TMPDIR\", \"/tmp\") // local fs that supports 0644 chmod\npath, err := writeTempAppendPromptFile(ccDataDir, prompt)","handlingStrategy":"validation","validationCode":"tmp := os.TempDir()\nprobe := filepath.Join(tmp, \".cc-probe\")\nif err := os.WriteFile(probe, []byte(\"x\"), 0o644); err != nil {\n    return fmt.Errorf(\"temp dir %s not writable with 0644: %w\", tmp, err)\n}\nos.Remove(probe)","typeGuard":null,"tryCatchPattern":"path, err := writeTempAppendPromptFile(ccDataDir, prompt)\nif err != nil {\n    if runtime.GOOS == \"windows\" || strings.Contains(err.Error(), \"chmod\") {\n        log.Warn(\"chmod unsupported on this fs; ensure TMPDIR is a local volume\")\n    }\n    return fmt.Errorf(\"prompt file setup failed: %w\", err)\n}","preventionTips":["Point TMPDIR at a local filesystem that supports POSIX modes (avoid exotic network mounts).","On Windows, expect Chmod semantics differences; prefer builds handling that explicitly.","When using run_as_user, ensure the temp dir is traversable by the target user (e.g. /tmp).","Pre-startup probe: write+chmod a scratch file to validate the temp dir before spawning sessions."],"tags":["claudecode","filesystem","permissions"],"backgroundTag":"file-write-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}