chenhg5/cc-connect · error

stdin pipe: %w

Error message

stdin pipe: %w

What it means

startRPC builds the exec.Cmd for the pi RPC subprocess and requests an stdin pipe via cmd.StdinPipe(). If the OS returns an error creating the pipe, it is wrapped as 'stdin pipe: %w'. This is a very low-level failure — pipe allocation happens almost exclusively inside os/exec.

Source

Thrown at agent/pi/session.go:196

		args = append(args, "--model", s.model)
	}
	if s.thinking != "" {
		args = append(args, "--thinking", s.thinking)
	}

	slog.Debug("piSession: starting RPC", "cmd", s.cmd, "args", args)

	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)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check for leaked pi/zombie subprocesses from prior sessions and kill them; investigate why sessions aren't being Stop()'d.
  2. Raise the file descriptor limit: `ulimit -n 4096` or set LimitNOFILE=4096 in the systemd unit.
  3. Restart cc-connect to release leaked descriptors, then fix the leak that exhausted them.
  4. Inspect the wrapped cause (%w) to confirm EMFILE/ENFILE and monitor open fds with `ls /proc/<pid>/fd | wc -l`.

Example fix

// systemd unit, before
[Service]
ExecStart=/usr/local/bin/cc-connect
// after
[Service]
LimitNOFILE=8192
ExecStart=/usr/local/bin/cc-connect
Defensive patterns

Strategy: retry

Validate before calling

var lim syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err == nil && lim.Cur < 1024 {
    log.Printf("low fd limit (%d); raise before spawning many pi sessions", lim.Cur)
}
nFds, _ := countOpenFds(os.Getpid())
if nFds > int(float64(lim.Cur)*0.8) {
    return fmt.Errorf("fd usage %d/%d too high to spawn pi safely", nFds, lim.Cur)
}

Try / catch

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

Prevention

When it happens

Trigger: startRPC (called from newPiSession) when cmd.StdinPipe() fails — practically only on OS resource exhaustion: file descriptor limit hit (too many open files/processes), or the os/exec package already reported the command started/waited state.

Common situations: Daemons leaking subprocesses/pipes until ulimit -n is exhausted; containers with tiny fd limits; runaway session creation loops leaving zombie pi processes holding descriptors.

Related errors


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