Wei-Shaw/sub2api · error

xAI device approval did not reach done page

Error message

xAI device approval did not reach done page

What it means

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.

Source

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

	}
	if !strings.Contains(finalURL, "consent") {
		return nil, errors.New("xAI device verification did not reach consent page")
	}

	status, finalURL, _, err = f.do(ctx, http.MethodPost, SSOApproveURL, url.Values{
		"user_code":      {device.UserCode},
		"action":         {"allow"},
		"principal_type": {"User"},
		"principal_id":   {""},
	})
	if err != nil {
		return nil, err
	}
	if status < 200 || status >= 400 {
		return nil, fmt.Errorf("approve xAI device code: %w", SSOHTTPError{Status: status})
	}
	if !strings.Contains(finalURL, "done") {
		return nil, errors.New("xAI device approval did not reach done page")
	}

	return f.pollToken(ctx, device.DeviceCode, time.Duration(device.Interval)*time.Second, time.Duration(device.ExpiresIn)*time.Second)
}

func (f *ssoDeviceFlow) pollToken(ctx context.Context, deviceCode string, interval, expiresIn time.Duration) (*TokenResponse, error) {
	if interval < time.Second {
		interval = time.Second
	}
	deadline := time.Now().Add(minDuration(expiresIn, 75*time.Second))
	for time.Now().Before(deadline) {
		if err := f.sleep(ctx, interval); err != nil {
			return nil, err
		}
		status, _, body, err := f.do(ctx, http.MethodPost, SSOTokenURL, url.Values{
			"grant_type":  {"urn:ietf:params:oauth:grant-type:device_code"},
			"client_id":   {DefaultClientID},
			"device_code": {deviceCode},

View on GitHub (pinned to 073e92d171)

Solutions

  1. Retry the full flow with a fresh device code to rule out stale cookies/state.
  2. Log the final URL and response body to identify which step xAI inserted or renamed.
  3. Update the approve form values in sso_device.go if xAI changed required parameters.
  4. Confirm the account is a plain 'User' principal; if workspace selection is required, populate principal_id accordingly.

Example fix

// before
if !strings.Contains(finalURL, "done") {
    return nil, errors.New("xAI device approval did not reach done page")
}

// after
if !strings.Contains(finalURL, "done") {
    return nil, fmt.Errorf("xAI device approval did not reach done page (landed on %s)", finalURL)
}
Defensive patterns

Strategy: retry

Try / catch

if err := flow.Authorize(ctx); err != nil {
    if strings.Contains(err.Error(), "did not reach done page") {
        return restartFlowWithFreshToken() // approval state lost; device_code is single-shot
    }
    return err
}

Prevention

When it happens

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

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

Related errors


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