aaif-goose/goose · error

Failed to exchange code: {} - {}

Error message

Failed to exchange code: {} - {}

What it means

During Tetrate Agent Router Service (TARS) signup, the received authorization code is exchanged at the token endpoint with goose-identifying headers (X-Title: goose). A non-success HTTP status is surfaced as this error carrying the status code and the endpoint's response body, which are also printed to stderr.

Source

Thrown at crates/goose/src/config/signup_tetrate/mod.rs:101

        eprintln!("Code: {}", code);
        eprintln!("Code verifier length: {}", self.code_verifier.len());
        eprintln!("Code challenge: {}", self.code_challenge);

        let response = client
            .post(TETRATE_TOKEN_URL)
            .header("X-Title", "goose")
            .header("Referer", "https://github.com/aaif-goose/goose")
            .json(&request_body)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            eprintln!("Token exchange failed!");
            eprintln!("Status: {}", status);
            eprintln!("Error response: {}", error_text);
            return Err(anyhow!(
                "Failed to exchange code: {} - {}",
                status,
                error_text
            ));
        }

        let token_response: TokenResponse = response.json().await?;
        Ok(token_response.key)
    }

    /// Complete flow: start server, open browser, wait for callback, exchange code
    pub async fn complete_flow(&mut self) -> Result<String> {
        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)).await?;
        let port = listener.local_addr()?.port();

        let (code_tx, code_rx) = oneshot::channel::<String>();
        let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
        self.server_shutdown_tx = Some(shutdown_tx);

View on GitHub (pinned to 3810898a74)

Solutions

  1. Inspect the embedded status and error body — invalid/expired code means you must rerun complete_flow() to obtain a fresh code and verifier
  2. Perform request and exchange within one flow object so code_verifier matches the code_challenge sent originally
  3. Retry the full flow later for transient 5xx responses
Defensive patterns

Strategy: retry

Try / catch

match flow.exchange_code(code).await {
    Err(e) if e.to_string().starts_with("Failed to exchange code") => {
        // parse status/body from the message: spent/expired code -> rerun complete_flow();
        // transient upstream 5xx -> retry the full flow after a short delay
    }
    other => other?,
}

Prevention

When it happens

Trigger: The token POST returns 4xx/5xx: expired or already-redeemed code, PKCE verifier mismatch (verifier belongs to a different flow instance than the code), clock skew, or a TARS-side outage.

Common situations: Re-running exchange_code with a code that was already consumed; resuming an interrupted flow with a stale verifier; TARS temporarily unavailable.

Related errors


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