chenhg5/cc-connect · error

read stdout: %w

Error message

read stdout: %w

What it means

agent/pi/session.go:294 — readLoopRPC's bufio.Scanner hit an error while reading the RPC child's stdout (not EOF) and the loop emits core.Event{Type:EventError, Error:"read stdout: %w"} on the session's events channel. This indicates the persistent RPC process's stdout stream broke mid-session rather than closing cleanly. The session is then marked dead (alive=false) after stderr reporting.

Source

Thrown at agent/pi/session.go:294

			slog.Debug("piSession: non-JSON line", "line", truncStr(line, 100))
			continue
		}

		s.handleEvent(raw)

		if !stateFetched && s.sessionIDReady() {
			stateFetched = true
			close(s.rpcReady)
		}
	}

	// Process exited — reap the child and signal the engine.
	// killRPC (now with Wait()) ensures the zombie is collected.
	s.killRPC()

	if err := scanner.Err(); err != nil {
		slog.Error("piSession: scanner error", "error", err)
		evt := core.Event{Type: core.EventError, Error: fmt.Errorf("read stdout: %w", err)}
		select {
		case s.events <- evt:
		case <-s.ctx.Done():
		}
	}

	// Signal process death to the engine (unless Close() already did).
	// Following the claudecode finishReadLoop pattern: always set alive=false,
	// and emit EventError with the captured stderr when present.
	// All writes to s.events happen before the deferred wg.Done(), so
	// Close()'s wg.Wait() → close(s.events) is correctly ordered.
	stderrMsg := strings.TrimSpace(s.stderrBuf.String())
	if stderrMsg != "" {
		evt := core.Event{Type: core.EventError, Error: fmt.Errorf("pi: %s", stderrMsg)}
		select {
		case s.events <- evt:
		case <-s.ctx.Done():
		}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check for bufio.ErrTooLong in the wrapped error; reduce pi output size (truncate tool/file output in prompts) since lines are capped at 10 MiB.
  2. Check system logs (dmesg / journalctl) for OOM kills of the pi process and raise memory limits.
  3. Verify pi is not being killed externally (systemd KillMode, container runtime OOMKilled status).
  4. Create a fresh session via /new or /switch — the old session is dead after this event; retry the prompt.
Defensive patterns

Strategy: type-guard

Type guard

func isStdoutReadError(err error) bool {
    return err != nil && (strings.Contains(err.Error(), "read stdout:") || errors.Is(err, bufio.ErrTooLong))
}

Try / catch

for evt := range session.Events() {
    if evt.Type == core.EventError && evt.Error != nil {
        if errors.Is(evt.Error, bufio.ErrTooLong) {
            slog.Warn("pi output line exceeded 10MiB; reduce tool/file output")
            continue
        }
        slog.Error("pi stdout stream failed; session dead", "err", evt.Error)
        // recreate session before next prompt
    }
}

Prevention

When it happens

Trigger: Called from startRPC's readLoopRPC goroutine when: (1) the child's stdout pipe is closed/destroyed abnormally (child segfaults, OOM-killed); (2) a read syscall returns EIO because the process died; (3) the scanner's buffer limit (10 MiB per line) is exceeded — scanner.Err() returns bufio.ErrTooLong; (4) OS-level pipe errors.

Common situations: pi emitting a single JSON line larger than 10 MiB (huge tool output or pasted file); pi process killed by OOM killer or `kill -9`; container memory limits terminating pi; disk/device I/O errors.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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