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
- 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.
- Check system logs (dmesg / journalctl) for OOM kills of the pi process and raise memory limits.
- Verify pi is not being killed externally (systemd KillMode, container runtime OOMKilled status).
- 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
- Avoid prompts that make pi dump huge files/outputs in a single event.
- Monitor host/container memory to prevent OOM kills of pi.
- Don't send SIGKILL to pi; use the library's Close/killRPC paths.
- Handle EventError in the event loop by recreating the session, not retrying on the dead one.
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.