chenhg5/cc-connect · error
pi: %s: %w
Error message
pi: %s: %w
What it means
agent/pi/session.go:419 — after the one-shot JSON-mode pi process finishes, sendJSON calls cmd.Wait(); if the exit status is non-zero it emits core.Event{Type:EventError, Error:"pi: <trimmed stderr>: <wait err>"} on the events channel (Send itself still returns nil — delivery of the error is via the event stream). This means pi ran but failed during the turn: provider errors, panics, or bad arguments.
Source
Thrown at agent/pi/session.go:419
scanner := bufio.NewScanner(stdout)
scanner.Buffer(make([]byte, 0, 64*1024), 10*1024*1024)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var raw map[string]any
if err := json.Unmarshal([]byte(line), &raw); err != nil {
slog.Debug("piSession: non-JSON line", "line", truncStr(line, 100))
continue
}
s.handleEvent(raw)
}
err = cmd.Wait()
if err != nil {
slog.Error("piSession: process error", "cmd", s.cmd, "error", err, "stderr", stderrBuf.String())
evt := core.Event{Type: core.EventError, Error: fmt.Errorf("pi: %s: %w", strings.TrimSpace(stderrBuf.String()), err)}
select {
case s.events <- evt:
case <-s.ctx.Done():
}
}
// Signal turn completion. Flush a deferred terminal error first in
// case the process exited without a final non-retry agent_end (the
// agent_end handler normally flushes pendingErr already).
if s.pendingErr != "" {
errEvt := core.Event{Type: core.EventError, Error: fmt.Errorf("%s", s.pendingErr)}
s.pendingErr = ""
select {
case s.events <- errEvt:
case <-s.ctx.Done():
}
}
sid := s.CurrentSessionID()View on GitHub (pinned to 4000b2338a)
Solutions
- Read the stderr text after "pi: " in the event to identify pi's own failure and fix it (credentials, quota, flags).
- Verify provider API keys are valid and set in the daemon environment.
- Retry the message; transient provider/network errors often succeed on retry.
- If it's a pi bug (panic trace), upgrade pi; if flags are the cause, correct model/thinking/session settings in config.
Defensive patterns
Strategy: try-catch
Type guard
func piWaitErrorEvent(evt core.Event) (stderr string, err error, ok bool) {
if evt.Type != core.EventError || evt.Error == nil {
return "", nil, false
}
s := evt.Error.Error()
if rest, found := strings.CutPrefix(s, "pi: "); found && strings.Contains(rest, ": ") {
parts := strings.SplitN(rest, ": ", 2)
return parts[0], evt.Error, true
}
return "", nil, false
} Try / catch
for evt := range session.Events() {
if stderr, err, ok := piWaitErrorEvent(evt); ok {
slog.Error("pi json turn failed", "stderr", stderr, "err", err)
if isTransient(err) { retryWithBackoff() } else { notifyUser(stderr) }
}
} Prevention
- Validate provider API keys and quotas before long turns.
- Keep prompts within provider limits; offload large content to files.
- Retry transient provider failures with backoff.
- Upgrade pi when panics appear in stderr; pin a known-good version.
When it happens
Trigger: sendJSON (via Send, non-rpc mode) when the pi one-shot process exits non-zero: (1) provider/API failure inside pi (quota, auth, network); (2) pi panics mid-turn; (3) invalid flags produced by buildJSONArgs (bad session id, model, thinking value); (4) context cancellation killing the process.
Common situations: Expired or missing provider API key; rate limiting or provider outage; a huge prompt exceeding provider limits; pi crashing on malformed session state resumed via CurrentSessionID; signal/timeout cancellation (CommandContext).
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/f973ec0755bcb93c.
Report an issue: GitHub.