Hmbown/CodeWhale · error

OAuth device-code request failed

Error message

OAuth device-code request failed ({detail})

What it means

Thrown when the OAuth 2.0 Device Authorization Grant request (RFC 8628) returns a non-success HTTP status or an OAuth error payload. The helper `oauth_failure_detail` builds a human-readable detail from the provider's `error`/`error_description` fields, falling back to the HTTP status. This library throws it so callers get a single classified message instead of raw HTTP output.

Solutions

  1. Check the provider's client_id and scopes configured for this OAuth provider entry
  2. Use the `detail` in the message: match the provider's `error` code (e.g. invalid_client means wrong client credentials)
  3. Re-run `codewhale` provider discovery or fix the issuer URL so the correct device-authorization endpoint is resolved
  4. Retry later if the status is 5xx (provider-side outage)

Example fix

// before (generic failure)
let grant = device_code_login(provider)?;
// after (inspect detail first, fix credentials)
match device_code_login(provider) {
    Ok(grant) => grant,
    Err(e) if e.to_string().contains("invalid_client") => {
        eprintln!("fix client_id for this provider");
        return Err(e);
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure client_id and scopes are set before requesting the grant
if client_id.trim().is_empty() || scopes.trim().is_empty() {
    return Err("client_id and scopes must be configured for device login");
}

Try / catch

try {
    let grant = device_code_login(provider).await?;
} catch (e) {
    // message contains provider failure detail (error code or HTTP status)
    logError("device grant failed", e);
    if (isRetryableStatus(e)) scheduleRetry(); else showReLoginHint(provider);
}

Prevention

When it happens

Trigger: Calling `request_device_grant` (via `device_code_login`) when the device-authorization endpoint returns status >= 400 or a body containing an `error` field, e.g. `invalid_client`, `slow_down`, or an expired/incorrect client_id.

Common situations: Wrong or stale `client_id` configured for the provider; issuer/discovery resolving to the wrong tenant; provider requiring scopes the client is not allowed; network proxy returning 4xx/5xx; provider outage returning 500 with an error body.

Related errors


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

Appendix: source

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

    let device_authorization_endpoint = oauth_endpoint_url(device_authorization_endpoint)?;
    let client = oauth_http_client("device-code")?;
    let params = [("client_id", client_id), ("scope", scopes)];
    #[cfg(test)]
    crate::external_credentials::record_oauth_network();
    let response = client
        .post(device_authorization_endpoint)
        .form(&params)
        .send()
        .context("OAuth device-code request failed")?;
    let (status, body): (_, DeviceGrantResponse) =
        parse_oauth_json(response, "OAuth device-code request")?;
    if !status.is_success() || body.error.is_some() {
        let detail = oauth_failure_detail(
            body.error.as_deref(),
            body.error_description.as_deref(),
            status,
        );
        bail!("OAuth device-code request failed ({detail})");
    }
    if body
        .device_code
        .as_deref()
        .is_some_and(|code| !code.trim().is_empty())
        && body
            .user_code
            .as_deref()
            .is_some_and(|code| !code.trim().is_empty())
    {
        return Ok(body);
    }
    bail!("OAuth device-code request returned success without a device and user code");
}

/// Poll the token endpoint once, classifying the RFC 8628 outcome. Matches
/// the legacy per-provider poll so the ported tests pin identical behavior.
fn poll_device_grant(

View on GitHub (pinned to 73e0f67d83)