{"record":{"id":"08413936e6d7c739","repo":"charmbracelet/crush","slug":"authorization-timed-out","errorCode":null,"errorMessage":"authorization timed out","messagePattern":"authorization timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/oauth/copilot/oauth.go","lineNumber":97,"sourceCode":"\t\tcase <-ticker.C:\n\t\t}\n\n\t\ttoken, err := tryGetToken(ctx, dc.DeviceCode)\n\t\tif err == errPending {\n\t\t\tcontinue\n\t\t}\n\t\tif err == errSlowDown {\n\t\t\tinterval += 5\n\t\t\tticker.Reset(time.Duration(interval) * time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn token, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"authorization timed out\")\n}\n\nvar (\n\terrPending  = fmt.Errorf(\"pending\")\n\terrSlowDown = fmt.Errorf(\"slow_down\")\n)\n\nfunc tryGetToken(ctx context.Context, deviceCode string) (*oauth.Token, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", clientID)\n\tdata.Set(\"device_code\", deviceCode)\n\tdata.Set(\"grant_type\", \"urn:ietf:params:oauth:grant-type:device_code\")\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", accessTokenURL, strings.NewReader(data.Encode()))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/oauth/copilot/oauth.go#L79-L115","documentation":"PollForToken polls the token endpoint until the device code expires; if the polling loop finishes without receiving a token (interval budget exhausted), it returns the sentinel \"authorization timed out\". The device code's expiresIn seconds elapsed while the user never completed browser authorization.","triggerScenarios":"The loop's elapsed time exceeds the device code's expires_in value before the user completes login; each iteration returns errPending/errSlowDown until the deadline passes.","commonSituations":"User is away from the machine or ignores the browser prompt; user takes longer than the ~15 minute expiry; slow_down responses repeatedly lengthen the interval so fewer attempts fit before expiry.","solutions":["Restart the login flow to get a fresh device code and complete authorization promptly","Open the verification URL in the default browser immediately and enter the code before expiry","Increase the effective deadline by restarting if your workflow is slow; report if expires_in is suspiciously short","Automate the browser-open step so no manual URL copying delays authorization"],"exampleFix":"// before\ntok, err := PollForToken(ctx, dc)\n// after\ntok, err := PollForToken(ctx, dc)\nif err != nil && err.Error() == \"authorization timed out\" {\n    // restart device flow for a fresh code\n    dc, _ = RequestDeviceCode(ctx)\n    tok, err = PollForToken(ctx, dc)\n}","handlingStrategy":"try-catch","validationCode":"if time.Since(dc.CreatedAt) > time.Duration(dc.ExpiresIn)*time.Second {\n    return fmt.Errorf(\"device code already expired; restart login\")\n}","typeGuard":null,"tryCatchPattern":"tok, err := PollForToken(ctx, dc)\nif err != nil && err.Error() == \"authorization timed out\" {\n    dc, _ = RequestDeviceCode(ctx)\n    tok, err = PollForToken(ctx, dc)\n}","preventionTips":["Open the verification URL in the browser automatically at flow start","Warn the user how long the code remains valid","Restart the device flow automatically when expiry is imminent and nothing is pending"],"tags":["oauth","device-flow","timeout","copilot"],"backgroundTag":"oauth-device-flow-timeout","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}