{"record":{"id":"19ecf076a011d6e0","repo":"Wei-Shaw/sub2api","slug":"xai-device-flow-token-polling-timed-out","errorCode":null,"errorMessage":"xAI device flow token polling timed out","messagePattern":"xAI device flow token polling timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/internal/pkg/xai/sso_device.go","lineNumber":241,"sourceCode":"\t\t\t\tScope:        payload.Scope,\n\t\t\t}, nil\n\t\t}\n\t\tswitch payload.Error {\n\t\tcase \"authorization_pending\":\n\t\t\tcontinue\n\t\tcase \"slow_down\":\n\t\t\tinterval += 5 * time.Second\n\t\t\tcontinue\n\t\tcase \"access_denied\", \"expired_token\":\n\t\t\treturn nil, ErrSSOAuthorizationDenied\n\t\tdefault:\n\t\t\tif status >= 400 {\n\t\t\t\treturn nil, fmt.Errorf(\"xAI token polling failed (%s): %w\", firstNonEmpty(payload.ErrorDescription, payload.Error), SSOHTTPError{Status: status})\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"xAI token polling failed: %s\", firstNonEmpty(payload.ErrorDescription, payload.Error, strconv.Itoa(status)))\n\t\t}\n\t}\n\treturn nil, errors.New(\"xAI device flow token polling timed out\")\n}\n\nfunc (f *ssoDeviceFlow) do(ctx context.Context, method, endpoint string, form url.Values) (int, string, []byte, error) {\n\tif !safeXAIAuthURL(endpoint) {\n\t\treturn 0, \"\", nil, errors.New(\"xAI OAuth URL is not trusted\")\n\t}\n\tcurrentURL := endpoint\n\tcurrentMethod := method\n\tcurrentForm := form\n\tfor redirects := 0; redirects <= 8; redirects++ {\n\t\tvar body io.Reader\n\t\tif currentForm != nil {\n\t\t\tbody = strings.NewReader(currentForm.Encode())\n\t\t}\n\t\trequest, err := http.NewRequestWithContext(ctx, currentMethod, currentURL, body)\n\t\tif err != nil {\n\t\t\treturn 0, currentURL, nil, err\n\t\t}","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/backend/internal/pkg/xai/sso_device.go#L223-L259","documentation":"pollToken loops POST-ing the device_code to the token endpoint until the deadline, which is capped at 75 seconds regardless of the advertised expires_in. If the deadline passes without an authorization_pending turning into a token, this timeout error is returned. Slow users (MFA, distracted) routinely exceed 75s on a flow xAI advertises as valid for ~30 minutes.","triggerScenarios":"User takes longer than min(expires_in, 75s) to approve; or xAI keeps answering authorization_pending because the approval in step [66] did not actually register. Also hit when the caller's ctx is fine but approval simply never happened.","commonSituations":"Automated flows with no human watching; approval done but cookies lost so xAI still reports pending; users who step away from the browser; CI runs that cannot approve at all.","solutions":["Approve the device prompt promptly (within ~75 seconds) and retry.","Verify step [66] genuinely reached the 'done' page; if approval silently failed, polling will always time out.","If you maintain this code, raise the minDuration cap (75*time.Second) to a more realistic bound for interactive users.","Treat this error as retryable at the orchestration layer: restart the whole device flow rather than re-polling a dead device_code."],"exampleFix":"// before\ndeadline := time.Now().Add(minDuration(expiresIn, 75*time.Second))\n\n// after (if interactive approval is expected)\ndeadline := time.Now().Add(minDuration(expiresIn, 5*time.Minute))","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"tok, err := flow.Token(ctx)\nif err != nil && strings.Contains(err.Error(), \"token polling timed out\") {\n    // device_code is now spent; only a full restart can succeed\n    tok, err = runFullDeviceFlow(ctx)\n}","preventionTips":["Approve the browser prompt within ~75s of starting","Never re-poll with the same device_code after timeout","If you own the code, size the deadline to real human approval times"],"tags":["xai","sso","device-flow","timeout"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}