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

  1. Raise the nofile limit for the cc-connect process (ulimit -n / LimitNOFILE)
  2. Restart the daemon to release leaked descriptors; check lsof for leaked pipes
  3. Lower max concurrent agent sessions in config to bound pipe usage
  4. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/aaf32fa931357fbe. Report an issue: GitHub.