{"record":{"id":"bc7558d1f887a645","repo":"sipeed/picoclaw","slug":"authentication-timed-out-after-5-minutes","errorCode":null,"errorMessage":"authentication timed out after 5 minutes","messagePattern":"authentication timed out after 5 minutes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/auth/oauth.go","lineNumber":183,"sourceCode":"\t\treturn ExchangeCodeForTokens(cfg, result.code, pkce.CodeVerifier, redirectURI)\n\tcase manualInput := <-manualCh:\n\t\tif manualInput == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"manual input canceled\")\n\t\t}\n\t\t// Extract code from URL if it's a full URL\n\t\tcode := manualInput\n\t\tif strings.Contains(manualInput, \"?\") {\n\t\t\tu, err := url.Parse(manualInput)\n\t\t\tif err == nil {\n\t\t\t\tcode = u.Query().Get(\"code\")\n\t\t\t}\n\t\t}\n\t\tif code == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"could not find authorization code in input\")\n\t\t}\n\t\treturn ExchangeCodeForTokens(cfg, code, pkce.CodeVerifier, redirectURI)\n\tcase <-time.After(5 * time.Minute):\n\t\treturn nil, fmt.Errorf(\"authentication timed out after 5 minutes\")\n\t}\n}\n\nfunc oauthCallbackRedirectURI(port int) string {\n\treturn fmt.Sprintf(\"http://localhost:%d/auth/callback\", port)\n}\n\nfunc oauthCallbackHandler(state string, resultCh chan<- callbackResult) http.Handler {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/auth/callback\", func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Query().Get(\"state\") != state {\n\t\t\tresultCh <- callbackResult{err: fmt.Errorf(\"state mismatch\")}\n\t\t\thttp.Error(w, \"State mismatch\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tcode := r.URL.Query().Get(\"code\")\n\t\tif code == \"\" {","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L165-L201","documentation":"The whole interactive login window expired: neither the loopback browser callback nor the manual-paste channel delivered a code within 5 minutes (oauth.go:183, time.After(5 * time.Minute)). The OAuth code is short-lived anyway, so after this timeout the flow must restart from a fresh PKCE/state pair.","triggerScenarios":"User starts login, gets distracted; browser fails to open (no xdg-open/open on the host) so the user never sees the auth page; user completes consent only after 5+ minutes of fiddling with login/2FA.","commonSituations":"Headless boxes missing a browser opener; slow SSO/MFA journeys; SSH sessions where the printed URL had to be carried to another machine and the round trip exceeded 5 minutes.","solutions":["Re-run the login command and complete it promptly — a fresh state/PKCE pair is generated each run","If no browser opened, manually open the printed authorization URL immediately","Open the auth URL on a phone/other machine right away so the redirect lands within the window","For truly slow flows, prefer the device-code flow (RequestDeviceCode) which polls with the server-returned interval"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"cred, err := auth.LoginBrowserWithOptions(cfg, opts)\nif err != nil && strings.Contains(err.Error(), \"authentication timed out\") {\n    fmt.Println(\"5-minute window expired — restarting login; complete consent promptly\")\n    cred, err = auth.LoginBrowserWithOptions(cfg, opts) // fresh state/PKCE each run\n}\nif err != nil { return err }","preventionTips":["Complete consent within the 5-minute window; restart if it lapses","Open the printed auth URL immediately in headless setups","Use the device-code flow for slow MFA/SSO journeys"],"tags":["oauth","timeout","ux","login"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}