{"record":{"id":"d4f222ef37249de4","repo":"slopus/happy","slug":"token-exchange-failed-tokenresponse-statustext","errorCode":null,"errorMessage":"Token exchange failed: ${tokenResponse.statusText}","messagePattern":"Token exchange failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/commands/connect/authenticateClaude.ts","lineNumber":103,"sourceCode":"): Promise<ClaudeAuthTokens> {\n\n    // Exchange code for tokens\n    const tokenResponse = await fetch(TOKEN_URL, {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n            grant_type: 'authorization_code',\n            code: code,\n            redirect_uri: `http://localhost:${port}/callback`,\n            client_id: CLIENT_ID,\n            code_verifier: verifier,\n            state: state,\n        }),\n    });\n    if (!tokenResponse.ok) {\n        throw new Error(`Token exchange failed: ${tokenResponse.statusText}`);\n    }\n\n    // {\n    //     token_type: 'Bearer',\n    //     access_token: string,\n    //     expires_in: number,\n    //     refresh_token: string,\n    //     scope: 'user:inference',\n    //     organization: {\n    //       uuid: string,\n    //       name: string\n    //     },\n    //     account: {\n    //       uuid: string,\n    //       email_address: string\n    //     }\n    //   }\n    const tokenData = await tokenResponse.json() as any;","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/commands/connect/authenticateClaude.ts#L85-L121","documentation":"Thrown by exchangeCodeForTokens when the OAuth token endpoint responds with a non-OK HTTP status. The CLI sent the authorization code + PKCE verifier to Anthropic's token endpoint, and the server rejected it; only statusText is surfaced, not the response body.","triggerScenarios":"The POST to the token endpoint returns !response.ok — e.g. expired/already-used authorization code, PKCE code_verifier mismatch, wrong client_id, or server outage.","commonSituations":"User waits too long between auth-start and callback (code expired), retries the flow reusing a consumed code, clock skew invalidating the verifier, or a proxy/firewall mangling the request.","solutions":["Re-run the full `happy connect` / authentication flow to get a fresh authorization code","Check that the redirect callback URL/port matches the redirect_uri sent in the token request","Verify CLIENT_ID and token endpoint URL are current for your CLI version","Capture response body by logging tokenResponse.text() before throwing to see the server's reason"],"exampleFix":"// before\nif (!tokenResponse.ok) {\n    throw new Error(`Token exchange failed: ${tokenResponse.statusText}`);\n}\n// after\nif (!tokenResponse.ok) {\n    const detail = await tokenResponse.text();\n    throw new Error(`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}: ${detail}`);\n}","handlingStrategy":"try-catch","validationCode":"// Re-check prerequisites before calling the connect flow\nif (!navigator_online()) throw new Error('Network required for token exchange');\n// Ensure a fresh auth code: restart the flow instead of reusing a code","typeGuard":null,"tryCatchPattern":"try {\n  await authenticateClaude(tokens);\n} catch (err) {\n  if (String(err.message).startsWith('Token exchange failed')) {\n    // restart full auth flow to obtain a fresh authorization code\n    await startAuthFlow();\n  } else throw err;\n}","preventionTips":["Always start a fresh OAuth flow rather than retrying with a consumed code","Complete the browser approval promptly to avoid code expiry","Keep the CLI updated for current CLIENT_ID and endpoint config","Check connectivity/proxy settings before authenticating"],"tags":["oauth","network","authentication"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}