chenhg5/cc-connect · error

%s

Error message

%s

What it means

When the claude process terminates with an error, finishReadLoop extracts whatever the CLI wrote to stderr and relays it verbatim (fmt.Errorf("%s", stderrMsg)) as a core.EventError on the session event channel. This is the surface through which Claude Code's own failure output (bad flag, auth failure, version mismatch) reaches the engine/user.

Source

Thrown at agent/claudecode/session.go:559

		}
		_ = stdout.Close()
	}()

	return waitErrCh, waitDone
}

func (cs *claudeSession) finishReadLoop(waitErrCh <-chan error, stderrBuf *bytes.Buffer) {
	err := <-waitErrCh

	cs.alive.Store(false)
	if err != nil {
		stderrMsg := ""
		if stderrBuf != nil {
			stderrMsg = strings.TrimSpace(stderrBuf.String())
		}
		if stderrMsg != "" {
			slog.Error("claudeSession: process failed", "error", err, "stderr", stderrMsg)
			evt := core.Event{Type: core.EventError, Error: fmt.Errorf("%s", stderrMsg)}
			select {
			case cs.events <- evt:
			case <-cs.ctx.Done():
				// INVARIANT: readLoop must close cs.events and cs.done exactly once
				// on every termination path. Callers (engine event loop) rely on
				// these closures to observe session end.
			}
		}
	}
	close(cs.events)
	close(cs.done)
}

func (cs *claudeSession) handleReadLoopScanErr(err error, waitDone <-chan struct{}) {
	if err == nil {
		return
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the stderr text in the event message — it contains the CLI's own diagnosis and dictates the fix
  2. Run `claude --version` and `claude doctor`; upgrade or pin the CLI to a version compatible with cc-connect's flags
  3. Re-authenticate: run `claude login` (or fix ANTHROPIC_API_KEY in providerEnv) as the daemon user
  4. Reproduce manually with the same args/model from config to see the stderr directly
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight the CLI before creating sessions
func claudeHealthy(bin string) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    out, err := exec.CommandContext(ctx, bin, "--version").CombinedOutput()
    if err != nil {
        return fmt.Errorf("claude CLI check failed: %v: %s", err, out)
    }
    return nil
}

Try / catch

go func() {
    for evt := range sess.Events() {
        if evt.Type == core.EventError {
            // evt.Error wraps raw CLI stderr — show it and recreate session
            notifyUser("claude session failed: " + evt.Error.Error())
            sess = recreateSession()
        }
    }
}()

Prevention

When it happens

Trigger: The claude process exits non-zero or the scanner loop ends with an error while stderrBuf holds output — e.g. claude was invoked with an unsupported flag (e.g. after a CLI upgrade removed --replay), the API key/login expired, or the CLI crashed at startup.

Common situations: Expired Claude subscription/login requiring `claude login`; CLI version change breaking a flag; invalid model name in config; network/API outage causing the CLI to abort with a message on stderr.

Related errors


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