Wei-Shaw/sub2api · error

xAI device flow token polling timed out

Error message

xAI device flow token polling timed out

What it means

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.

Source

Thrown at backend/internal/pkg/xai/sso_device.go:241

				Scope:        payload.Scope,
			}, nil
		}
		switch payload.Error {
		case "authorization_pending":
			continue
		case "slow_down":
			interval += 5 * time.Second
			continue
		case "access_denied", "expired_token":
			return nil, ErrSSOAuthorizationDenied
		default:
			if status >= 400 {
				return nil, fmt.Errorf("xAI token polling failed (%s): %w", firstNonEmpty(payload.ErrorDescription, payload.Error), SSOHTTPError{Status: status})
			}
			return nil, fmt.Errorf("xAI token polling failed: %s", firstNonEmpty(payload.ErrorDescription, payload.Error, strconv.Itoa(status)))
		}
	}
	return nil, errors.New("xAI device flow token polling timed out")
}

func (f *ssoDeviceFlow) do(ctx context.Context, method, endpoint string, form url.Values) (int, string, []byte, error) {
	if !safeXAIAuthURL(endpoint) {
		return 0, "", nil, errors.New("xAI OAuth URL is not trusted")
	}
	currentURL := endpoint
	currentMethod := method
	currentForm := form
	for redirects := 0; redirects <= 8; redirects++ {
		var body io.Reader
		if currentForm != nil {
			body = strings.NewReader(currentForm.Encode())
		}
		request, err := http.NewRequestWithContext(ctx, currentMethod, currentURL, body)
		if err != nil {
			return 0, currentURL, nil, err
		}

View on GitHub (pinned to 073e92d171)

Solutions

  1. Approve the device prompt promptly (within ~75 seconds) and retry.
  2. Verify step [66] genuinely reached the 'done' page; if approval silently failed, polling will always time out.
  3. If you maintain this code, raise the minDuration cap (75*time.Second) to a more realistic bound for interactive users.
  4. Treat this error as retryable at the orchestration layer: restart the whole device flow rather than re-polling a dead device_code.

Example fix

// before
deadline := time.Now().Add(minDuration(expiresIn, 75*time.Second))

// after (if interactive approval is expected)
deadline := time.Now().Add(minDuration(expiresIn, 5*time.Minute))
Defensive patterns

Strategy: retry

Try / catch

tok, err := flow.Token(ctx)
if err != nil && strings.Contains(err.Error(), "token polling timed out") {
    // device_code is now spent; only a full restart can succeed
    tok, err = runFullDeviceFlow(ctx)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/19ecf076a011d6e0. Report an issue: GitHub.