{"record":{"id":"27590a76aac185df","repo":"aaif-goose/goose","slug":"failed-to-exchange-code","errorCode":null,"errorMessage":"Failed to exchange code: {} - {}","messagePattern":"Failed to exchange code: (.+?) - (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose/src/config/signup_openrouter/mod.rs","lineNumber":122,"sourceCode":"\n        eprintln!(\"Exchanging code for API key...\");\n        eprintln!(\"Code: {}\", code);\n        eprintln!(\"Code verifier length: {}\", self.code_verifier.len());\n        eprintln!(\"Code challenge: {}\", self.code_challenge);\n\n        let response = client\n            .post(OPENROUTER_TOKEN_URL)\n            .json(&request_body)\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            eprintln!(\"Token exchange failed!\");\n            eprintln!(\"Status: {}\", status);\n            eprintln!(\"Error response: {}\", error_text);\n            return Err(anyhow!(\n                \"Failed to exchange code: {} - {}\",\n                status,\n                error_text\n            ));\n        }\n\n        let token_response: TokenResponse = response.json().await?;\n        Ok(token_response.key)\n    }\n\n    /// Complete flow: open browser, wait for callback, exchange code\n    pub async fn complete_flow(&mut self) -> Result<String> {\n        let auth_url = self.get_auth_url();\n\n        println!(\"Opening browser for authentication...\");\n        eprintln!(\"Auth URL: {}\", auth_url);\n\n        if let Err(e) = webbrowser::open(&auth_url) {","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose/src/config/signup_openrouter/mod.rs#L104-L140","documentation":"After the OpenRouter OAuth callback, the PKCE authorization code is exchanged at OPENROUTER_TOKEN_URL together with the flow's code_verifier. A non-success HTTP status surfaces as this error carrying the status code and response body (both also printed to stderr), so the failure detail from the token endpoint is preserved.","triggerScenarios":"POST to the token endpoint returns 4xx/5xx: code already redeemed or expired (400 invalid_grant), code_verifier not matching this code (each SignupFlow generates its own verifier), clock skew, or an upstream 5xx.","commonSituations":"Retrying the exchange with a code that was already used; reusing a stale flow object after an interrupted signup; network flakiness or OpenRouter outage during exchange.","solutions":["Read the status and body in the message/stderr — 400 with invalid_grant means the code is spent or expired: restart the complete_flow() from scratch to get a fresh code+verifier pair","Ensure the exchange runs exactly once per code, inside the same SignupFlow instance that built the auth URL","For transient 5xx/network failures, wait briefly and retry the whole flow"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match flow.exchange_code(code).await {\n    Err(e) if e.to_string().starts_with(\"Failed to exchange code\") => {\n        // status+body are in the message. invalid_grant/expired => restart complete_flow()\n        // for a fresh code+verifier; transient 5xx => back off briefly and rerun the flow\n    }\n    other => other?,\n}","preventionTips":["Always exchange within the same SignupFlow instance that generated the auth URL so the PKCE verifier matches","Exchange each code exactly once, immediately after receiving the callback","Keep the exchange within the code's short validity window"],"tags":["auth","oauth","openrouter","network","http"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}