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

  1. Read the stderr text after "pi: " in the event to identify pi's own failure and fix it (credentials, quota, flags).
  2. Verify provider API keys are valid and set in the daemon environment.
  3. Retry the message; transient provider/network errors often succeed on retry.
  4. 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

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.