{"record":{"id":"6f11a8a372c89971","repo":"chenhg5/cc-connect","slug":"read-stdout-w-6f11a8","errorCode":null,"errorMessage":"read stdout: %w","messagePattern":"read stdout: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":294,"sourceCode":"\t\t\tslog.Debug(\"piSession: non-JSON line\", \"line\", truncStr(line, 100))\n\t\t\tcontinue\n\t\t}\n\n\t\ts.handleEvent(raw)\n\n\t\tif !stateFetched && s.sessionIDReady() {\n\t\t\tstateFetched = true\n\t\t\tclose(s.rpcReady)\n\t\t}\n\t}\n\n\t// Process exited — reap the child and signal the engine.\n\t// killRPC (now with Wait()) ensures the zombie is collected.\n\ts.killRPC()\n\n\tif err := scanner.Err(); err != nil {\n\t\tslog.Error(\"piSession: scanner error\", \"error\", err)\n\t\tevt := core.Event{Type: core.EventError, Error: fmt.Errorf(\"read stdout: %w\", err)}\n\t\tselect {\n\t\tcase s.events <- evt:\n\t\tcase <-s.ctx.Done():\n\t\t}\n\t}\n\n\t// Signal process death to the engine (unless Close() already did).\n\t// Following the claudecode finishReadLoop pattern: always set alive=false,\n\t// and emit EventError with the captured stderr when present.\n\t// All writes to s.events happen before the deferred wg.Done(), so\n\t// Close()'s wg.Wait() → close(s.events) is correctly ordered.\n\tstderrMsg := strings.TrimSpace(s.stderrBuf.String())\n\tif stderrMsg != \"\" {\n\t\tevt := core.Event{Type: core.EventError, Error: fmt.Errorf(\"pi: %s\", stderrMsg)}\n\t\tselect {\n\t\tcase s.events <- evt:\n\t\tcase <-s.ctx.Done():\n\t\t}","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L276-L312","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isStdoutReadError(err error) bool {\n    return err != nil && (strings.Contains(err.Error(), \"read stdout:\") || errors.Is(err, bufio.ErrTooLong))\n}","tryCatchPattern":"for evt := range session.Events() {\n    if evt.Type == core.EventError && evt.Error != nil {\n        if errors.Is(evt.Error, bufio.ErrTooLong) {\n            slog.Warn(\"pi output line exceeded 10MiB; reduce tool/file output\")\n            continue\n        }\n        slog.Error(\"pi stdout stream failed; session dead\", \"err\", evt.Error)\n        // recreate session before next prompt\n    }\n}","preventionTips":["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."],"tags":["stdout","scanner","process-crash","go","agent-pi"],"backgroundTag":"file-read-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}