{"record":{"id":"fd8b162f619515b4","repo":"chenhg5/cc-connect","slug":"claudesession-start-w","errorCode":null,"errorMessage":"claudeSession: start: %w","messagePattern":"claudeSession: start: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"agent/claudecode/session.go","lineNumber":469,"sourceCode":"\t// （下面 startReadLoopWait 对 stdout 已经做了 50ms 后强关来躲这个坑，\n\t//   注释里还写着 \"no descendants holding it\" —— 唯独 stderr 漏了同样的处理。）\n\t//\n\t// 证据：同一个仓库的 gemini / kimi / antigravity / hooks **四个适配器全都设了\n\t// WaitDelay**（gemini 那行注释：「确保 I/O goroutine 在 context 结束后不会长时间阻塞」）\n\t// —— 唯独 claudecode 漏了。这不是新发明，是补上一致性。\n\t//\n\t// 取 3s 而非兄弟们的 1s：Claude Code 退出时可能还在吐最后一段 stderr（报错栈），\n\t// 太短会截掉有用的诊断；而 Close() 的兜底等待是 10s，3s 留足余量。\n\t// 超时后 Wait() 返回 ErrWaitDelay 并强制关管道 —— stderr 可能不全，\n\t// 但**进程状态是准的**。这正是要的取舍：宁可少一段日志，不要一个杀不死的会话。\n\tcmd.WaitDelay = 3 * time.Second\n\n\tif err := cmd.Start(); err != nil {\n\t\tif promptFilePath != \"\" && !promptFileIsShared {\n\t\t\t_ = os.Remove(promptFilePath)\n\t\t}\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"claudeSession: start: %w\", err)\n\t}\n\n\t// Only remember the prompt path for cleanup when it is the per-spawn\n\t// temp variant. The shared cc-connect-system.md file is reused across\n\t// all sessions and must never be deleted by an individual session's\n\t// Close.\n\tvar cleanupPromptPath string\n\tif !promptFileIsShared {\n\t\tcleanupPromptPath = promptFilePath\n\t}\n\n\tcs := &claudeSession{\n\t\tcmd:                 cmd,\n\t\tstdin:               stdin,\n\t\tevents:              make(chan core.Event, 64),\n\t\tworkDir:             workDir,\n\t\tctx:                 sessionCtx,\n\t\tcancel:              cancel,","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/session.go#L451-L487","documentation":"newClaudeSession wraps the error from cmd.Start() — the `claude` CLI process could not be launched at all. The wrapped error (typically *exec.Error or *fs.PathError) names the root cause: binary not found, non-executable path, bad working directory, or missing environment. A temp prompt file created for this spawn is cleaned up and the context is cancelled before returning.","triggerScenarios":"StartSession → newClaudeSession → cmd.Start() fails: the configured claude binary path does not exist or is not executable, the working directory is invalid, or env/cgroup limits prevent fork/exec (EAGAIN under memory/pid limits).","commonSituations":"CLI not installed or not on PATH; config points at a wrong or renamed binary; Claude Code was updated and the path changed; container memory limits block exec; noexec-mounted filesystem for the binary path.","solutions":["Verify the binary: run the exact configured path manually (e.g. `claude --version`); fix the binary path in config.toml if wrong","Install/repair Claude Code CLI and ensure it is on PATH for the daemon user (`which claude` as the service user)","Check the wrapped error: exec.ErrNotFound → install binary; 'permission denied' → chmod +x or fix noexec mount; 'fork: cannot allocate memory' → raise limits","If using cc-connect doctor, run it to validate the agent CLI configuration"],"exampleFix":"// before (config.toml)\n[agents.claudecode]\ncommand = \"/usr/local/bin/claude\"  # uninstalled after upgrade\n// after\n[agents.claudecode]\ncommand = \"/home/user/.local/bin/claude\"","handlingStrategy":"validation","validationCode":"// before StartSession, verify the CLI is launchable\nbin := \"/home/user/.local/bin/claude\" // value from config\nif _, err := exec.LookPath(bin); err != nil {\n    return fmt.Errorf(\"claude CLI not found: %w\", err)\n}\nif fi, err := os.Stat(bin); err != nil || fi.IsDir() || fi.Mode()&0o111 == 0 {\n    return fmt.Errorf(\"claude CLI missing or not executable: %s\", bin)\n}","typeGuard":null,"tryCatchPattern":"sess, err := agent.StartSession(ctx, prompt)\nif err != nil {\n    var execErr *exec.Error\n    if errors.As(err, &execErr) {\n        // binary not found / not executable → surface config guidance to user\n        return fmt.Errorf(\"check 'command' in config.toml: %w\", err)\n    }\n    return err\n}","preventionTips":["Run `cc-connect doctor` (or `claude --version` as the daemon user) at startup and on a cron","Pin the claude CLI path in config.toml and re-verify after every CLI upgrade","Install the CLI system-wide or for the exact user running the daemon","Ensure the working directory configured for the agent exists and is accessible"],"tags":["go","os-exec","process-spawn","cli"],"backgroundTag":"command-not-found","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"}