{"record":{"id":"aaf32fa931357fbe","repo":"chenhg5/cc-connect","slug":"claudesession-stdout-pipe-w","errorCode":null,"errorMessage":"claudeSession: stdout pipe: %w","messagePattern":"claudeSession: stdout pipe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/session.go","lineNumber":433,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\tslog.Debug(\"claudeSession: spawn details\",\n\t\t\"bin\", cliBin,\n\t\t\"allArgs\", core.RedactArgs(allArgs),\n\t\t\"model\", model,\n\t\t\"providerEnv\", core.RedactEnv(providerEnvSnapshot))\n\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"claudeSession: stdin pipe: %w\", err)\n\t}\n\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"claudeSession: stdout pipe: %w\", err)\n\t}\n\n\tvar stderrBuf bytes.Buffer\n\tcmd.Stderr = &stderrBuf\n\n\t// 🔴 /stop「杀不死」的根因修复（2026-07-29）。\n\t//\n\t// 症状：taskkill /T /F 报成功，Close() 却在 10 秒后返回\n\t// \"process tree (pid N) still alive 10s after SIGKILL reported success\"，\n\t// engine 于是认定 teardown 失败 —— 用户按了 /stop，却像在跟另一个 session 说话。\n\t//\n\t// 机制：上面这行把 stderr 接到 *bytes.Buffer（不是 *os.File），os/exec 因此会\n\t// 自建一条管道 + 一个 io.Copy goroutine。**cmd.Wait() 不只等进程退出，还要等\n\t// 那个 goroutine 结束**，而它要等管道 EOF —— 只要**任何一个孙进程**\n\t// （Claude Code 拉起的 MCP server）继承了 stderr 写端还活着，管道就永不 EOF。\n\t// 于是：直接子进程早被杀死，Wait() 却永不返回 → cs.done 永不关闭 → Close() 只能超时。\n\t// **\"还活着\"的其实不是进程，是那根没人关的管道。**\n\t// （下面 startReadLoopWait 对 stdout 已经做了 50ms 后强关来躲这个坑，","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/session.go#L415-L451","documentation":"newClaudeSession wraps the error from cmd.StdoutPipe() when the stdout pipe for the `claude` process cannot be created. Like the stdin pipe failure, this indicates OS resource exhaustion (file descriptors) or a programming error (pipe requested after Start). The session creation is cancelled and the error is returned to StartSession.","triggerScenarios":"cmd.StdinPipe() succeeded but cmd.StdoutPipe() immediately failed — fd table full, or the exec.Cmd was reused after a previous Start (not the case here since a fresh cmd is built per session).","commonSituations":"Same as stdin pipe failure: fd exhaustion on long-lived daemons, tight container limits, fork/pipe pressure under heavy concurrent session load.","solutions":["Raise the nofile limit for the cc-connect process (ulimit -n / LimitNOFILE)","Restart the daemon to release leaked descriptors; check lsof for leaked pipes","Lower max concurrent agent sessions in config to bound pipe usage","Read the wrapped error (%w) to confirm the syscall (e.g. 'too many open files')"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// same fd-budget check as the stdin pipe case\nfunc fdsAvailable(min int) bool {\n    var lim syscall.Rlimit\n    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {\n        return false\n    }\n    return lim.Cur > uint64(min)\n}","typeGuard":null,"tryCatchPattern":"sess, err := agent.StartSession(ctx, prompt)\nif err != nil && strings.Contains(err.Error(), \"stdout pipe\") {\n    time.Sleep(2 * time.Second) // allow fds to free up\n    sess, err = agent.StartSession(ctx, prompt)\n}\nif err != nil { return err }","preventionTips":["Raise the nofile limit for the service user and systemd unit","Watch fd usage (lsof / /proc/<pid>/fd count) with alerting","Cap simultaneous sessions to keep pipe usage bounded","Fix any fd leaks in custom platform/agent plugins promptly"],"tags":["go","os-exec","process-spawn","file-descriptors"],"backgroundTag":"file-open-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}