chenhg5/cc-connect · error
claudeSession: stdout pipe: %w
Error message
claudeSession: stdout pipe: %w
What it means
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.
Source
Thrown at agent/claudecode/session.go:433
}
}
}
slog.Debug("claudeSession: spawn details",
"bin", cliBin,
"allArgs", core.RedactArgs(allArgs),
"model", model,
"providerEnv", core.RedactEnv(providerEnvSnapshot))
stdin, err := cmd.StdinPipe()
if err != nil {
cancel()
return nil, fmt.Errorf("claudeSession: stdin pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
return nil, fmt.Errorf("claudeSession: stdout pipe: %w", err)
}
var stderrBuf bytes.Buffer
cmd.Stderr = &stderrBuf
// 🔴 /stop「杀不死」的根因修复(2026-07-29)。
//
// 症状:taskkill /T /F 报成功,Close() 却在 10 秒后返回
// "process tree (pid N) still alive 10s after SIGKILL reported success",
// engine 于是认定 teardown 失败 —— 用户按了 /stop,却像在跟另一个 session 说话。
//
// 机制:上面这行把 stderr 接到 *bytes.Buffer(不是 *os.File),os/exec 因此会
// 自建一条管道 + 一个 io.Copy goroutine。**cmd.Wait() 不只等进程退出,还要等
// 那个 goroutine 结束**,而它要等管道 EOF —— 只要**任何一个孙进程**
// (Claude Code 拉起的 MCP server)继承了 stderr 写端还活着,管道就永不 EOF。
// 于是:直接子进程早被杀死,Wait() 却永不返回 → cs.done 永不关闭 → Close() 只能超时。
// **"还活着"的其实不是进程,是那根没人关的管道。**
// (下面 startReadLoopWait 对 stdout 已经做了 50ms 后强关来躲这个坑,View on GitHub (pinned to 4000b2338a)
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')
Defensive patterns
Strategy: fallback
Validate before calling
// same fd-budget check as the stdin pipe case
func fdsAvailable(min int) bool {
var lim syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
return false
}
return lim.Cur > uint64(min)
} Try / catch
sess, err := agent.StartSession(ctx, prompt)
if err != nil && strings.Contains(err.Error(), "stdout pipe") {
time.Sleep(2 * time.Second) // allow fds to free up
sess, err = agent.StartSession(ctx, prompt)
}
if err != nil { return err } Prevention
- 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
When it happens
Trigger: 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).
Common situations: Same as stdin pipe failure: fd exhaustion on long-lived daemons, tight container limits, fork/pipe pressure under heavy concurrent session load.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- claudeSession: stdin pipe: %w
- claudeSession: start: %w
- stdin pipe: %w
- stdout pipe: %w
- acp: probe stdin pipe: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/aaf32fa931357fbe.
Report an issue: GitHub.