chenhg5/cc-connect · error

stdout pipe: %w

Error message

stdout pipe: %w

What it means

startRPC requests an stdout pipe for the pi RPC subprocess via cmd.StdoutPipe(); any OS error creating it is wrapped as 'stdout pipe: %w'. Like the stdin-pipe error, this is a near-impossible-in-practice OS-level failure that indicates resource exhaustion or exec package misuse.

Source

Thrown at agent/pi/session.go:203

	cmd := exec.CommandContext(s.ctx, s.cmd, args...)
	cmd.Dir = s.workDir
	env := os.Environ()
	if len(s.extraEnv) > 0 {
		env = core.MergeEnv(env, s.extraEnv)
	}
	cmd.Env = env

	stdinPipe, err := cmd.StdinPipe()
	if err != nil {
		return fmt.Errorf("stdin pipe: %w", err)
	}
	s.rpcStdin = stdinPipe
	s.rpcCmd = cmd

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("stdout pipe: %w", err)
	}
	cmd.Stderr = &s.stderrBuf

	prepareCmdForKill(cmd)

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("start: %w", err)
	}

	s.wg.Add(1)
	go s.readLoopRPC(stdout)

	// Pi's RPC protocol does not push a "session" event on stdout — the only
	// way to learn the session id is to send {"type":"get_state"} and parse
	// the matching response in handleEvent. We probe immediately after spawn
	// so that newPiSession's wait on rpcReady only unblocks once the id has
	// been stored. readLoopRPC closes rpcReady as soon as sessionIDReady()
	// flips to true (which happens after handleEvent processes the response),

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Kill leaked pi subprocesses and audit session teardown so stopped sessions release their pipes.
  2. Raise the nofile limit (`ulimit -n` or LimitNOFILE in systemd) and restart cc-connect.
  3. Check the wrapped cause for EMFILE/ENFILE and count open fds per process to find the leak.
  4. If it persists without resource pressure, verify no custom code calls cmd.Start()/Wait() on the same exec.Cmd before startRPC finishes wiring pipes.

Example fix

// before: sessions never stopped, fds leak
// after: ensure teardown
func (e *Engine) shutdown() {
    for _, s := range e.sessions {
        _ = s.Stop() // closes rpc pipes, reaps pi process
    }
}
Defensive patterns

Strategy: retry

Validate before calling

var lim syscall.Rlimit
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
nFds, _ := countOpenFds(os.Getpid())
if nFds > int(float64(lim.Cur)*0.8) {
    return fmt.Errorf("fd usage %d/%d too high; restart daemon or raise limit before starting pi", nFds, lim.Cur)
}

Try / catch

sess, err := agent.StartSession(ctx, opts)
if err != nil && strings.Contains(err.Error(), "stdout pipe") {
    log.Printf("stdout pipe allocation failed; likely fd exhaustion — check ulimit -n and leaked processes")
    return fmt.Errorf("resource exhaustion starting pi: %w", err)
}

Prevention

When it happens

Trigger: startRPC (called from newPiSession) when cmd.StdoutPipe() fails — OS-level pipe/fd allocation failure, e.g. file descriptor limits (EMFILE/ENFILE) or memory pressure during pipe allocation.

Common situations: Same as stdin pipe failures: fd leaks from leaked pi processes, low LimitNOFILE in systemd/containers, fork bombs or runaway session loops exhausting descriptors.

Related errors


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