{"record":{"id":"71960a554bd456a4","repo":"Wei-Shaw/sub2api","slug":"xai-device-approval-did-not-reach-done-page","errorCode":null,"errorMessage":"xAI device approval did not reach done page","messagePattern":"xAI device approval did not reach done page","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/internal/pkg/xai/sso_device.go","lineNumber":174,"sourceCode":"\t}\n\tif !strings.Contains(finalURL, \"consent\") {\n\t\treturn nil, errors.New(\"xAI device verification did not reach consent page\")\n\t}\n\n\tstatus, finalURL, _, err = f.do(ctx, http.MethodPost, SSOApproveURL, url.Values{\n\t\t\"user_code\":      {device.UserCode},\n\t\t\"action\":         {\"allow\"},\n\t\t\"principal_type\": {\"User\"},\n\t\t\"principal_id\":   {\"\"},\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif status < 200 || status >= 400 {\n\t\treturn nil, fmt.Errorf(\"approve xAI device code: %w\", SSOHTTPError{Status: status})\n\t}\n\tif !strings.Contains(finalURL, \"done\") {\n\t\treturn nil, errors.New(\"xAI device approval did not reach done page\")\n\t}\n\n\treturn f.pollToken(ctx, device.DeviceCode, time.Duration(device.Interval)*time.Second, time.Duration(device.ExpiresIn)*time.Second)\n}\n\nfunc (f *ssoDeviceFlow) pollToken(ctx context.Context, deviceCode string, interval, expiresIn time.Duration) (*TokenResponse, error) {\n\tif interval < time.Second {\n\t\tinterval = time.Second\n\t}\n\tdeadline := time.Now().Add(minDuration(expiresIn, 75*time.Second))\n\tfor time.Now().Before(deadline) {\n\t\tif err := f.sleep(ctx, interval); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstatus, _, body, err := f.do(ctx, http.MethodPost, SSOTokenURL, url.Values{\n\t\t\t\"grant_type\":  {\"urn:ietf:params:oauth:grant-type:device_code\"},\n\t\t\t\"client_id\":   {DefaultClientID},\n\t\t\t\"device_code\": {deviceCode},","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/backend/internal/pkg/xai/sso_device.go#L156-L192","documentation":"After POST-ing approval (action=allow) to the approve endpoint, the flow requires the final URL to contain 'done'. If the approval POST returns 2xx/3xx but lands anywhere else, approval did not complete: the consent was denied server-side, a form field changed, or an intermediate step was inserted. Token polling is never started in this case.","triggerScenarios":"POST to SSOApproveURL with {user_code, action=allow, principal_type=User, principal_id=\"\"} returns a redirect chain that ends on a non-'done' URL: changed form parameter names, an account-selection step now required (principal_id no longer empty), or consent actually declined.","commonSituations":"xAI alters the approve form (new hidden fields, renamed action value); org accounts requiring a real principal_id; cookie loss between verify and approve; A/B variants of the consent UI.","solutions":["Retry the full flow with a fresh device code to rule out stale cookies/state.","Log the final URL and response body to identify which step xAI inserted or renamed.","Update the approve form values in sso_device.go if xAI changed required parameters.","Confirm the account is a plain 'User' principal; if workspace selection is required, populate principal_id accordingly."],"exampleFix":"// before\nif !strings.Contains(finalURL, \"done\") {\n    return nil, errors.New(\"xAI device approval did not reach done page\")\n}\n\n// after\nif !strings.Contains(finalURL, \"done\") {\n    return nil, fmt.Errorf(\"xAI device approval did not reach done page (landed on %s)\", finalURL)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := flow.Authorize(ctx); err != nil {\n    if strings.Contains(err.Error(), \"did not reach done page\") {\n        return restartFlowWithFreshToken() // approval state lost; device_code is single-shot\n    }\n    return err\n}","preventionTips":["Treat verify+approve as one unit; on any miss, restart the whole flow","Keep cookies alive between verify and approve","Monitor for xAI UI changes (alert on this error rate)"],"tags":["xai","sso","device-flow","redirect"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}