Hmbown/CodeWhale · error

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

Error message

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

What it means

Entry point for the browser-based PKCE login. Before binding any listener or opening a browser, it checks whether the provider defines an `authorize_path`; providers without a browser flow (xAI) are rejected up front with a message pointing at the device-code alternative.

Solutions

  1. Run the device-code login instead: `codewhale auth xai-device`.
  2. Select a provider that supports the browser flow if browser sign-in is what you want.
  3. If adding a new provider, define `authorize_path` in its `OAuthProviderParams` to enable browser login.

Example fix

// before
let pending = pkce_login(OAuthProvider::Xai).await?;   // bails
// after
let pending = device_code_login(OAuthProvider::Xai).await?;  // or codewhale auth xai-device
Defensive patterns

Strategy: fallback

Validate before calling

let params = oauth_provider_params(provider);
let use_browser = params.authorize_path.is_some();
if !use_browser { return device_code_login(provider).await; }

Type guard

fn has_browser_login(provider: OAuthProvider) -> bool {
    oauth_provider_params(provider).authorize_path.is_some()
}

Prevention

When it happens

Trigger: Calling `pkce_login(provider)` where `oauth_provider_params(provider).authorize_path` is `None` — currently the xAI provider.

Common situations: Running the generic browser login command with a device-code-only provider selected; scripting `pkce_login` for xAI; provider registry entry lacking `authorize_path`.

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/8450766e126004a8. Report an issue: GitHub.

Appendix: source

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

        &[
            ("grant_type", "authorization_code"),
            ("client_id", client_id),
            ("redirect_uri", redirect_uri),
            ("code", code),
            ("code_verifier", verifier),
        ],
    )?;
    parse_oauth_form_response(status, &body, "authorization code exchange", params)
}

/// Interactive PKCE browser login for any provider whose row offers it.
/// Prints the authorize URL, opens a browser, and waits for the loopback
/// callback. A provider with no browser flow (xAI) fails here with the
/// reason, before any listener binds.
pub async fn pkce_login(provider: OAuthProvider) -> Result<PendingOAuthLogin> {
    let params = oauth_provider_params(provider);
    if params.authorize_path.is_none() {
        bail!(
            "{} offers no browser sign-in flow; sign in through the device-code login instead",
            params.display_name
        );
    }
    let inputs = params.resolve_inputs();
    let display_name = params.display_name;
    tokio::task::spawn_blocking(move || pkce_login_with(provider, &inputs))
        .await
        .with_context(|| format!("{display_name} PKCE login worker failed"))?
}

/// Blocking worker body for [`pkce_login`]. `pub(crate)` so the activation
/// tests can drive the unified login end to end until activation unifies.
pub(crate) fn pkce_login_with(
    provider: OAuthProvider,
    inputs: &ResolvedOAuthInputs,
) -> Result<PendingOAuthLogin> {
    let params = oauth_provider_params(provider);

View on GitHub (pinned to 73e0f67d83)