chenhg5/cc-connect · error

copilot probe: stdout pipe: %w

Error message

copilot probe: stdout pipe: %w

What it means

This error wraps the failure to create an stdout pipe on the `copilot` CLI process that the probe session spawns to list/delete sessions via LSP-style JSON-RPC. It is thrown by newProbeSession when cmd.StdoutPipe() returns an error, before the process is started. It almost always indicates an OS-level resource problem (too many open files, exhausted pipes) rather than a copilot configuration issue.

Source

Thrown at agent/copilot/copilot.go:223

// newProbeSession spawns a copilot --headless --stdio probe process, starts a
// read loop, and returns a probeSession. Caller must call close() when done.
func newProbeSession(ctx context.Context, snapshot probeSnapshot) (*probeSession, error) {
	probeCtx, cancel := context.WithCancel(ctx)

	cmd := exec.CommandContext(probeCtx, snapshot.cmd, "--headless", "--stdio", "--no-auto-update")
	cmd.Dir = snapshot.workDir
	cmd.Env = snapshot.env

	stdin, err := cmd.StdinPipe()
	if err != nil {
		cancel()
		return nil, fmt.Errorf("copilot probe: stdin pipe: %w", err)
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		cancel()
		return nil, fmt.Errorf("copilot probe: stdout pipe: %w", err)
	}
	cmd.Stderr = io.Discard

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

	rpc := newRPCClient(stdin)
	reader := newLSPReader(stdout)
	done := make(chan struct{})

	go func() {
		defer func() {
			_ = stdin.Close()
			_ = cmd.Wait()
			close(done)
		}()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check for leaked copilot processes / probe sessions not being closed (LookPath/spawn leaks) and fix the leak
  2. Raise the file-descriptor limit (ulimit -n) for the cc-connect daemon process
  3. In containers, raise RLIMIT_NOFILE (e.g. docker --ulimit nofile=65536:65536)
  4. Retry the ListSessions/DeleteSession call once resources are freed

Example fix

// before
cmd := exec.CommandContext(ctx, bin, args...)
stdout, err := cmd.StdoutPipe() // fails under fd exhaustion
// after
// ensure previous probe cmd.Wait() is always called via defer so pipes close
func (a *Agent) newProbeSession(ctx context.Context) (*probeSession, error) {
    ctx, cancel := context.WithTimeout(ctx, probeTimeout)
    defer func() { ... }()
    cmd := exec.CommandContext(ctx, bin, args...)
    stdout, err := cmd.StdoutPipe()
    if err != nil { cancel(); return nil, fmt.Errorf("copilot probe: stdout pipe: %w", err) }
    ...
}
Defensive patterns

Strategy: retry

Validate before calling

// before calling ListSessions/DeleteSession
func fdsAvailable(minFree uint64) bool {
    entries, err := os.ReadDir("/proc/self/fd")
    if err != nil { return true }
    var rlim syscall.Rlimit
    syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlim)
    return uint64(len(entries))+minFree < rlim.Cur
}

Try / catch

err := a.ListSessions(ctx)
if err != nil && strings.Contains(err.Error(), "stdout pipe") {
    slog.Warn("fd exhaustion suspected; retrying after GC/close", "err", err)
    runtime.GC()
    err = a.ListSessions(ctx)
}

Prevention

When it happens

Trigger: Calling ListSessions or DeleteSession when the os/exec StdoutPipe call fails, typically because the process has exhausted its file-descriptor limit (EMFILE) or the pipe syscall fails.

Common situations: Long-running cc-connect daemons leaking copilot probe processes and hitting the ulimit -n / RLIMIT_NOFILE ceiling; containers with low fd limits; running under a heavily loaded system where pipe allocation fails.

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/3343ee308a55a093. Report an issue: GitHub.