chenhg5/cc-connect · error
copilotSession: handshake failed: %w
Error message
copilotSession: handshake failed: %w
What it means
After the process starts, newCopilotSession performs a handshake (ping, then create/resume session). If handshake returns any error, the session is closed and the whole construction fails with "copilotSession: handshake failed: %w". This is the umbrella wrapper — inspect the wrapped error (ping error, ping timeout, or session-create error) for the real cause.
Source
Thrown at agent/copilot/session.go:153
ctx: sessionCtx,
cancel: cancel,
done: make(chan struct{}),
pendingPermissions: make(map[string]json.RawMessage),
eventPermissions: make(map[string]struct{}),
}
cs.alive.Store(true)
cs.autoApprove.Store(mode == "bypassPermissions")
if resumeSessionID != "" && resumeSessionID != core.ContinueSession {
cs.sessionID.Store(resumeSessionID)
}
// Start reading loop
go cs.readLoop(&stderrBuf)
// Perform handshake: ping then create/resume session
if err := cs.handshake(resumeSessionID); err != nil {
_ = cs.Close()
return nil, fmt.Errorf("copilotSession: handshake failed: %w", err)
}
return cs, nil
}
func (cs *copilotSession) handshake(resumeSessionID string) error {
// Step 1: Ping
_, pingCh := cs.rpc.call("ping", nil)
select {
case resp := <-pingCh:
if resp.Error != nil {
return fmt.Errorf("ping: %w", resp.Error)
}
slog.Debug("copilotSession: ping OK")
case <-time.After(10 * time.Second):
return fmt.Errorf("ping timeout")
case <-cs.ctx.Done():
return cs.ctx.Err()View on GitHub (pinned to 4000b2338a)
Solutions
- Look at the wrapped inner error and the copilot CLI stderr for the root cause
- Re-authenticate the copilot CLI (expired GitHub auth is the most common cause of a dead-but-running child)
- Check CLI version compatibility — upgrade/downgrade copilot so ping and session methods exist
- If resuming, drop the stale session ID and start a fresh session
- Verify network access to the GitHub Copilot backend from the daemon host
Example fix
// before: resuming a session that died earlier
s, err := StartSession(ctx, cfg, lastSessionID)
// after: fall back to a fresh session on resume failure
s, err := StartSession(ctx, cfg, lastSessionID)
if err != nil && strings.Contains(err.Error(), "handshake failed") {
s, err = StartSession(ctx, cfg, "")
} Defensive patterns
Strategy: fallback
Validate before calling
// verify CLI is authenticated and responsive before session start
out, err := exec.CommandContext(ctx, copilotPath, "--version").Output()
if err != nil {
return fmt.Errorf("copilot CLI not runnable: %w", err)
} Try / catch
sess, err := StartSession(ctx, cfg, resumeID)
if err != nil {
if strings.Contains(err.Error(), "handshake failed") {
// inspect the wrapped cause, then fall back to a fresh session
slog.Warn("copilot: handshake failed, retrying fresh session", "err", err)
sess, err = StartSession(ctx, cfg, "")
}
}
return sess, err Prevention
- Pre-authenticate the CLI as the daemon user so startup is non-interactive
- Keep the copilot CLI version pinned and tested
- Drop stale session IDs proactively; don't resume ids older than the CLI's lifetime
- Log the child's stderr buffer on every handshake failure
When it happens
Trigger: StartSession → newCopilotSession where handshake fails: the copilot process started but never answered `ping` within 10s, returned a JSON-RPC error for ping, or failed the session/create (or resume) step.
Common situations: Copilot CLI crashed right after start (bad API token, network blocked); wrong CLI version that doesn't implement `ping` or the session methods; authentication expired (copilot not logged in); resuming a sessionID that no longer exists.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- ping timeout
- copilot: session.delete: %s
- copilot: session.delete failed: %s
- copilot: session.delete failed: unknown error
- ping: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/150dee3cf089d33c.
Report an issue: GitHub.