Hmbown/CodeWhale · error

{}

Error message

{}

What it means

The OAuth device-code polling loop (`DeviceCodePoller::run`) reached its lifetime deadline before the authorization flow completed, so it gives up with a timeout error. The message is the poller's configured `timeout_message`, or the `slow_down_timeout_message` instead when the server sent at least one `slow_down` response (a hint of client clock drift, e.g. in WSL/VMs). This is a deliberate terminal condition, not a transport failure.

Solutions

  1. Complete the device-code authorization in the browser promptly and restart the login flow.
  2. Increase the poller's `lifetime` (builder setting) to give users more time.
  3. If the slow_down-specific message appears, resync the system clock (e.g. restart WSL or run an NTP sync) and retry.
  4. Check network access to the auth server so polls actually reach Pending/Complete instead of stalling.

Example fix

// before
DeviceCodePoller::new(endpoint).lifetime(Duration::from_secs(60))
// after
DeviceCodePoller::new(endpoint).lifetime(Duration::from_secs(600))
Defensive patterns

Strategy: try-catch

Validate before calling

let lifetime = Duration::from_secs(600);
assert!(lifetime > Duration::from_secs(60), "device-code lifetime too short for interactive login");

Try / catch

match poller.run(sleep, poll) {
    Err(err) if err.to_string().contains("timed out") => eprintln!("Login not completed in time; rerun and authorize promptly."),
    Err(err) => return Err(err),
    Ok(token) => store(token),
}

Prevention

When it happens

Trigger: Calling `DeviceCodePoller::run(sleep, poll)` when the user never completes browser authorization (or the token endpoint keeps returning Pending/SlowDown) before `lifetime` elapses; also when `wait_before_first_poll` is set and the remaining lifetime is already zero.

Common situations: User abandons the login flow in the browser; user takes too long to enter the device code; system clock drifts badly inside WSL or a suspended VM causing repeated `slow_down` responses until the deadline passes; `lifetime` configured too short for slow users.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/aec415307d2d45c7. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/device_code.rs:174

                    };
                }
            }

            // Never sleep past the code's expiry, even after slow_down backoff.
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.is_zero() {
                break;
            }
            sleep(interval.min(remaining));
        }

        Err(self.timed_out(saw_slow_down))
    }

    fn timed_out(&self, saw_slow_down: bool) -> anyhow::Error {
        match (saw_slow_down, self.slow_down_timeout_message.as_deref()) {
            (true, Some(message)) => anyhow::anyhow!("{message}"),
            _ => anyhow::anyhow!("{}", self.timeout_message),
        }
    }
}

/// Reject a device-code verification URI that must not be handed to a browser
/// opener.
///
/// Ported from pi's `validateVerificationUri`
/// (`packages/ai/src/auth/oauth/xai.ts`, MIT, Copyright (c) 2025 Mario
/// Zechner): the URI comes straight off the wire and is passed to the platform
/// "open this" call, so a malicious or compromised response could otherwise
/// launch `file:`, a custom app scheme, or a helper with attacker-chosen
/// arguments. pi requires `https:`; Codewhale additionally allows `http:` on a
/// loopback host, which is what self-hosted issuers and the device-code tests
/// use — matching the loopback allowance the account login already makes.
///
/// Embedded credentials are rejected in every case.
pub fn validate_browser_verification_uri(raw: &str, context: &str) -> Result<String> {

View on GitHub (pinned to 433685b202)