aaif-goose/goose · error
Failed to receive authorization code
Error message
Failed to receive authorization code
What it means
During OpenRouter signup, a localhost callback server (run_callback_server) forwards the OAuth authorization code over a tokio channel. The receiver gets Ok(Err(_)) — this error — when the channel's sender is dropped without delivering a code, i.e. the server task exited before receiving the callback. It is distinct from the sibling timeout error (AUTH_TIMEOUT elapsed).
Source
Thrown at crates/goose/src/config/signup_openrouter/mod.rs:91
/// Start local server and wait for callback
pub async fn start_server(&mut self) -> Result<String> {
let (code_tx, code_rx) = oneshot::channel::<String>();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
// Store shutdown sender so we can stop the server later
self.server_shutdown_tx = Some(shutdown_tx);
// Start the server in a background task
tokio::spawn(async move {
if let Err(e) = server::run_callback_server(code_tx, shutdown_rx).await {
eprintln!("Server error: {}", e);
}
});
// Wait for the authorization code with timeout
match timeout(AUTH_TIMEOUT, code_rx).await {
Ok(Ok(code)) => Ok(code),
Ok(Err(_)) => Err(anyhow!("Failed to receive authorization code")),
Err(_) => Err(anyhow!("Authentication timeout - please try again")),
}
}
pub async fn exchange_code(&self, code: String) -> Result<String> {
let client = Client::new();
let request_body = TokenRequest {
code: code.clone(),
code_verifier: self.code_verifier.clone(),
code_challenge_method: "S256".to_string(),
};
eprintln!("Exchanging code for API key...");
eprintln!("Code: {}", code);
eprintln!("Code verifier length: {}", self.code_verifier.len());
eprintln!("Code challenge: {}", self.code_challenge);
View on GitHub (pinned to 3810898a74)
Solutions
- Check stderr for the `Server error: ...` line — for a bind failure, free the callback port (stop the other flow / kill the stale process) and retry
- Do not run two signup flows at once on the same machine
- If the server error persists, retry after a moment — a fresh flow re-binds the listener
Defensive patterns
Strategy: retry
Validate before calling
// Before starting the flow, confirm the callback port is free:
let listener = std::net::TcpListener::bind(("127.0.0.1", port));
if listener.is_err() {
// another signup flow owns the port: abort with a clear message instead of a channel drop
}
drop(listener); Try / catch
match signup.wait_for_code().await {
Err(e) if e.to_string() == "Failed to receive authorization code" => {
// server task died (see stderr 'Server error'); free the port and restart the whole flow
}
Err(e) if e.to_string().starts_with("Authentication timeout") => {
// user never completed the browser step; restart flow and remind them to finish it
}
other => other?,
} Prevention
- Run one signup flow at a time per machine
- Check stderr for the 'Server error:' line whenever this fires — it holds the bind/panic cause
- Ensure local loopback listeners are permitted in sandboxed environments
When it happens
Trigger: The spawned server task fails to bind the callback port (already in use) or panics, dropping code_tx; the browser never reaches the callback because the server is gone. The real cause is printed to stderr as `Server error: ...`.
Common situations: A previous signup attempt left a process holding the callback port; running multiple `goose signup` flows concurrently; sandboxed environments where the local listener fails to start.
Related errors
- Failed to exchange code: {} - {}
- Failed to exchange code: {} - {}
- Failed to receive authorization code
- Failed to fetch OIDC config: ${configResp.status}
- Failed to fetch JWKS: ${jwksResp.status}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/58fb4bfda4283873.
Report an issue: GitHub.