nikivdev/code · error

device code expired. Run `f auth` again.

Error message

device code expired. Run `f auth` again.

What it means

When the poll loop receives status "expired" from the auth server — or the retry deadline elapses without approval — login aborts telling the user to run 'f auth' again. Device codes are short-lived by design; this is the normal 'user took too long to authorize' termination path.

Source

Thrown at src/auth.rs:100

        if !poll_response.status().is_success() {
            bail!("device auth poll failed: HTTP {}", poll_response.status());
        }

        let poll: DevicePollResponse = poll_response
            .json()
            .context("failed to parse device auth poll response")?;

        match poll.status.as_str() {
            "approved" => {
                let token = poll
                    .token
                    .ok_or_else(|| anyhow!("device auth approved without token"))?;
                env::save_ai_auth_token(token, Some(api_url.clone()))?;
                println!("✓ Auth complete. You're ready to use Flow AI.");
                return Ok(());
            }
            "pending" => continue,
            "expired" => bail!("device code expired. Run `f auth` again."),
            "invalid" => bail!("device code invalid. Run `f auth` again."),
            other => bail!("unexpected auth status: {}", other),
        }
    }

    bail!("device code expired. Run `f auth` again.")
}

fn open_in_browser(url: &str) {
    #[cfg(target_os = "macos")]
    {
        let _ = std::process::Command::new("open").arg(url).status();
    }

    #[cfg(target_os = "linux")]
    {
        let _ = std::process::Command::new("xdg-open").arg(url).status();
    }

View on GitHub (pinned to a747e741ae)

Solutions

  1. Run 'f auth' again to get a fresh device code (this is the only remedy once expired)
  2. Authorize promptly: open the verification URL as soon as it's displayed
  3. Check system clock (NTP) if expiry seems premature
  4. Extend the client-side retry deadline if your auth server's codes last longer than the loop budget

Example fix

// before
"expired" => bail!("device code expired. Run `f auth` again."),
// after
"expired" => {
    eprintln!("device code expired; requesting a new one...");
    return login(api_url); // restart flow with a fresh device code
}
Defensive patterns

Strategy: retry

Validate before calling

// Track elapsed time and warn the user before expiry
let deadline = Instant::now() + code_ttl;
if Instant::now() > deadline - Duration::from_secs(60) {
    eprintln!("Device code is about to expire — authorize now!");
}

Try / catch

match login(&api_url) {
    Err(e) if e.to_string().contains("device code expired") => {
        eprintln!("Code expired; starting a new login...");
        return login(&api_url); // one automatic re-attempt
    }
    other => other,
}

Prevention

When it happens

Trigger: Poll status equals "expired", or the loop exhausts its iterations/time budget and falls through to the final bail at the end of login.

Common situations: User waits past the code's TTL before opening the verification URL; machine clock skew makes the client stop polling early; polling loop bound too tight for slow approvals.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/a8ea2d62b49545c7. Report an issue: GitHub.