chenhg5/cc-connect · error
pi: %s
Error message
pi: %s
What it means
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.
Source
Thrown at agent/pi/session.go:308
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():
}
}
s.alive.Store(false)
}
// ── Send ─────────────────────────────────────────────────────
// Send writes a prompt to the Pi agent.
// In json mode (default): spawns a one-shot `pi --mode json` process.
// In rpc mode: writes a "prompt" command to the persistent RPC process stdin.
func (s *piSession) Send(msg string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
s.sendWg.Add(1)
defer s.sendWg.Done()
if !s.alive.Load() {View on GitHub (pinned to 4000b2338a)
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.
Defensive patterns
Strategy: fallback
Type guard
func piStderrEvent(evt core.Event) (string, bool) {
if evt.Type != core.EventError || evt.Error == nil {
return "", false
}
s := evt.Error.Error()
if rest, ok := strings.CutPrefix(s, "pi: "); ok && !strings.Contains(rest, ":") {
return rest, true
}
return "", false
} Try / catch
for evt := range session.Events() {
if msg, ok := piStderrEvent(evt); ok {
slog.Error("pi exited with stderr", "stderr", msg)
notifyUser("Agent crashed: " + msg)
recreateSession()
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- pi: %s: %w
- piSession: write get_state probe: %w
- piSession: marshal command: %w
- iflow process failed: %w
- %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/3550075dc787f4a3.
Report an issue: GitHub.