Hmbown/CodeWhale · error

offers no sign-in flow

Error message

{} offers no sign-in flow

What it means

Thrown by the top-level sign-in dispatcher when a provider has neither `device_code_path` nor `authorize_path`, meaning no sign-in flow exists at all for it. This is a configuration/code-table invariant guard rather than a network failure.

Solutions

  1. Check the provider name you passed against the supported provider list in `oauth_provider_params`
  2. Use the provider's non-OAuth auth method (e.g. API key) if it defines no OAuth flows
  3. Add `device_code_path` or `authorize_path` to the provider row if you maintain this provider definition
Defensive patterns

Strategy: validation

Validate before calling

// ensure the provider defines at least one sign-in flow
const params = oauthProviderParams(provider);
if (!params?.device_code_path && !params?.authorize_path) {
  throw new Error(`${provider} has no OAuth sign-in flow configured`);
}

Type guard

function hasSignInFlow(provider) {
  const p = oauthProviderParams(provider);
  return p != null && (p.device_code_path != null || p.authorize_path != null);
}

Try / catch

try {
  await signIn(provider);
} catch (e) {
  if (String(e).includes('offers no sign-in flow')) {
    console.error(`No OAuth flow for ${provider}; use API-key auth instead.`);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the sign-in entry point with a provider whose `oauth_provider_params` row defines neither a device-code path nor a PKCE authorize path (or an unknown/typo'd provider mapping).

Common situations: Passing an invalid or newly-added provider name not yet wired into `oauth_provider_params`; provider intentionally login-less (API-key only) being used where OAuth sign-in is requested.

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/65cdcd5c4ccf88f2. Report an issue: GitHub.

Appendix: source

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

    Ok(PendingOAuthLogin {
        provider,
        issuer: inputs.issuer.clone(),
        client_id: inputs.client_id.clone(),
        token,
    })
}

/// One login entry point for every provider: the params row decides whether
/// the grant is device-code or browser PKCE. A provider with neither fails
/// here with the reason.
pub async fn login(provider: OAuthProvider) -> Result<PendingOAuthLogin> {
    let params = oauth_provider_params(provider);
    if params.device_code_path.is_some() {
        device_code_login(provider).await
    } else if params.authorize_path.is_some() {
        pkce_login(provider).await
    } else {
        bail!("{} offers no sign-in flow", params.display_name);
    }
}

// ── form-post transport seam ──────────────────────────────────────────
//
// One seam for every OAuth form post (PKCE exchange, refresh, revoke): the
// production client is reqwest with the shared bounds; tests substitute a
// mock issuer. The seam records network/refresh in test builds so the
// side-effect trap can still prove "zero external I/O" assertions.

pub(crate) trait OAuthFormClient {
    fn post_form(&self, url: &str, form: &[(&str, &str)]) -> Result<(u16, String)>;
}

pub(crate) struct ReqwestOAuthFormClient;

impl OAuthFormClient for ReqwestOAuthFormClient {
    fn post_form(&self, url: &str, form: &[(&str, &str)]) -> Result<(u16, String)> {

View on GitHub (pinned to 73e0f67d83)