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

  1. 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
  2. Do not run two signup flows at once on the same machine
  3. 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

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


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/58fb4bfda4283873. Report an issue: GitHub.