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

  1. Look at the wrapped inner error and the copilot CLI stderr for the root cause
  2. Re-authenticate the copilot CLI (expired GitHub auth is the most common cause of a dead-but-running child)
  3. Check CLI version compatibility — upgrade/downgrade copilot so ping and session methods exist
  4. If resuming, drop the stale session ID and start a fresh session
  5. 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

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

Related errors


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