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

  1. 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).
  2. Run `pi` interactively in the same workDir to reproduce and see the error directly.
  3. Check pi's config files and provider credentials; upgrade/downgrade pi if a panic indicates a pi bug.
  4. 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

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


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