{"record":{"id":"150dee3cf089d33c","repo":"chenhg5/cc-connect","slug":"copilotsession-handshake-failed-w","errorCode":null,"errorMessage":"copilotSession: handshake failed: %w","messagePattern":"copilotSession: handshake failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/copilot/session.go","lineNumber":153,"sourceCode":"\t\tctx:                sessionCtx,\n\t\tcancel:             cancel,\n\t\tdone:               make(chan struct{}),\n\t\tpendingPermissions: make(map[string]json.RawMessage),\n\t\teventPermissions:   make(map[string]struct{}),\n\t}\n\tcs.alive.Store(true)\n\tcs.autoApprove.Store(mode == \"bypassPermissions\")\n\tif resumeSessionID != \"\" && resumeSessionID != core.ContinueSession {\n\t\tcs.sessionID.Store(resumeSessionID)\n\t}\n\n\t// Start reading loop\n\tgo cs.readLoop(&stderrBuf)\n\n\t// Perform handshake: ping then create/resume session\n\tif err := cs.handshake(resumeSessionID); err != nil {\n\t\t_ = cs.Close()\n\t\treturn nil, fmt.Errorf(\"copilotSession: handshake failed: %w\", err)\n\t}\n\n\treturn cs, nil\n}\n\nfunc (cs *copilotSession) handshake(resumeSessionID string) error {\n\t// Step 1: Ping\n\t_, pingCh := cs.rpc.call(\"ping\", nil)\n\tselect {\n\tcase resp := <-pingCh:\n\t\tif resp.Error != nil {\n\t\t\treturn fmt.Errorf(\"ping: %w\", resp.Error)\n\t\t}\n\t\tslog.Debug(\"copilotSession: ping OK\")\n\tcase <-time.After(10 * time.Second):\n\t\treturn fmt.Errorf(\"ping timeout\")\n\tcase <-cs.ctx.Done():\n\t\treturn cs.ctx.Err()","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/copilot/session.go#L135-L171","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: resuming a session that died earlier\ns, err := StartSession(ctx, cfg, lastSessionID)\n// after: fall back to a fresh session on resume failure\ns, err := StartSession(ctx, cfg, lastSessionID)\nif err != nil && strings.Contains(err.Error(), \"handshake failed\") {\n    s, err = StartSession(ctx, cfg, \"\")\n}","handlingStrategy":"fallback","validationCode":"// verify CLI is authenticated and responsive before session start\nout, err := exec.CommandContext(ctx, copilotPath, \"--version\").Output()\nif err != nil {\n    return fmt.Errorf(\"copilot CLI not runnable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"sess, err := StartSession(ctx, cfg, resumeID)\nif err != nil {\n    if strings.Contains(err.Error(), \"handshake failed\") {\n        // inspect the wrapped cause, then fall back to a fresh session\n        slog.Warn(\"copilot: handshake failed, retrying fresh session\", \"err\", err)\n        sess, err = StartSession(ctx, cfg, \"\")\n    }\n}\nreturn sess, err","preventionTips":["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"],"tags":["handshake","jsonrpc","copilot","startup"],"backgroundTag":"handshake-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}