{"record":{"id":"3550075dc787f4a3","repo":"chenhg5/cc-connect","slug":"pi-s","errorCode":null,"errorMessage":"pi: %s","messagePattern":"pi: %s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":308,"sourceCode":"\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}\n\t}\n\ts.alive.Store(false)\n}\n\n// ── Send ─────────────────────────────────────────────────────\n\n// Send writes a prompt to the Pi agent.\n// In json mode (default): spawns a one-shot `pi --mode json` process.\n// In rpc mode: writes a \"prompt\" command to the persistent RPC process stdin.\nfunc (s *piSession) Send(msg string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {\n\ts.sendWg.Add(1)\n\tdefer s.sendWg.Done()\n\n\tif !s.alive.Load() {","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L290-L326","documentation":"agent/pi/session.go:308 — when the RPC process exits, readLoopRPC captures everything pi wrote to stderr and, if non-empty, emits core.Event{Type:EventError, Error:\"pi: <stderr text>\"}. This is how pi's own error output (panic traces, config errors, provider errors) reaches the engine. It always accompanies the RPC process terminating; alive is set false right after.","triggerScenarios":"readLoopRPC (started by startRPC) ends its scan loop and the captured stderr buffer is non-empty: pi printed any error/panic/warning to stderr before exiting, pi was killed, or pi exited due to invalid configuration or provider failures.","commonSituations":"Missing or invalid pi provider API key; pi panicking on a malformed session file; pi version mismatch after upgrade; pi rate-limited by its backend and exiting with a message; user running /stop or Close() while pi had pending stderr output.","solutions":["Read the message after \"pi: \" — it is pi's verbatim stderr; fix the underlying pi-side issue it describes (bad key, bad config, exhausted quota).","Run `pi` interactively in the same workDir to reproduce and see the error directly.","Check pi's config files and provider credentials; upgrade/downgrade pi if a panic indicates a pi bug.","Start a new session; this session is terminated (alive=false) once this event fires."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"func piStderrEvent(evt core.Event) (string, bool) {\n    if evt.Type != core.EventError || evt.Error == nil {\n        return \"\", false\n    }\n    s := evt.Error.Error()\n    if rest, ok := strings.CutPrefix(s, \"pi: \"); ok && !strings.Contains(rest, \":\") {\n        return rest, true\n    }\n    return \"\", false\n}","tryCatchPattern":"for evt := range session.Events() {\n    if msg, ok := piStderrEvent(evt); ok {\n        slog.Error(\"pi exited with stderr\", \"stderr\", msg)\n        notifyUser(\"Agent crashed: \" + msg)\n        recreateSession()\n    }\n}","preventionTips":["Keep pi provider credentials valid and rotate before expiry.","Watch daemon logs for recurring pi stderr to catch config drift early.","Test pi interactively in the project workDir after upgrades.","Treat any EventError from the RPC session as terminal and recreate the session."],"tags":["stderr","process-exit","rpc","go","agent-pi"],"backgroundTag":"upstream-api-error","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"}