Hmbown/CodeWhale · warning

offers no device-code flow; sign in through the browser…

Error message

{display_name} offers no device-code flow; sign in through the browser login instead

What it means

The discovery-time twin of the static guard: even though the provider table lists a device flow, the resolved/discovered endpoint metadata contains no `device_authorization_endpoint`. Thrown in the blocking discovery helper before the grant request is made.

Solutions

  1. Point the issuer at the correct tenant that supports device authorization
  2. Enable RFC 8628 device flow on your identity provider (e.g. in Keycloak/Auth0/Entra admin settings)
  3. Refresh the discovery metadata (clear cache / correct well-known URL)
  4. Fall back to the browser (PKCE) login
Defensive patterns

Strategy: fallback

Validate before calling

// resolve endpoints first and branch
const endpoints = resolveOAuthEndpoints(params, issuer);
if (!endpoints.device_authorization_endpoint) {
  return pkceLogin(provider); // discovered metadata lacks device flow
}

Try / catch

try {
  await startDeviceLogin(provider);
} catch (e) {
  if (String(e).includes('no device-code flow')) {
    await pkceLogin(provider);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the endpoint-resolution helper when `resolve_oauth_endpoints` returns `device_authorization_endpoint: None` — the issuer's metadata (or configured override) does not advertise a device authorization endpoint.

Common situations: Issuer URL pointing at a tenant/instance that does not support device flow (e.g. some SSO tenants disable it); self-hosted identity provider without RFC 8628 enabled; stale cached discovery document.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/94aac96a55432d3e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:833

    let inputs = params.resolve_inputs();
    let display_name = params.display_name;
    tokio::task::spawn_blocking(move || device_code_login_with(provider, &inputs))
        .await
        .with_context(|| format!("{display_name} device-code login worker failed"))?
}

/// Blocking worker body for [`device_code_login`]. `pub(crate)` so the
/// legacy activation tests can drive the unified login end to end until
/// activation unifies in 3b-ii.
pub(crate) fn device_code_login_with(
    provider: OAuthProvider,
    inputs: &ResolvedOAuthInputs,
) -> Result<PendingOAuthLogin> {
    let params = oauth_provider_params(provider);
    let display_name = params.display_name;
    let endpoints = resolve_oauth_endpoints(params, &inputs.issuer);
    let Some(device_endpoint) = endpoints.device_authorization_endpoint else {
        bail!(
            "{display_name} offers no device-code flow; sign in through the browser login instead"
        );
    };
    let token_endpoint = endpoints.token_endpoint;
    let poll_floor_secs = params.device_poll_floor_secs;
    let grant = request_device_grant(&device_endpoint, &inputs.client_id, &inputs.scopes)?;
    let verify = grant
        .verification_uri_complete
        .clone()
        .or(grant.verification_uri.clone())
        .unwrap_or_else(|| format!("{}/device", inputs.issuer.trim_end_matches('/')));
    // Off the wire, headed for `webbrowser::open`: must be a bare
    // navigation, never a scheme or credential smuggle.
    let verify = codewhale_config::device_code::validate_browser_verification_uri(
        &verify,
        &format!("{display_name} device-code request"),
    )?;
    let user_code = grant.user_code.unwrap_or_default();

View on GitHub (pinned to 73e0f67d83)