{"record":{"id":"30876d9e7763259c","repo":"decolua/9router","slug":"callbackparams-error-description-callbackpara-30876d","errorCode":null,"errorMessage":"${callbackParams.error_description || callbackParams.error}","messagePattern":"\\$\\{callbackParams\\.error_description \\|\\| callbackParams\\.error\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/codex.js","lineNumber":120,"sourceCode":"\n      await new Promise((resolve, reject) => {\n        const timeout = setTimeout(() => {\n          reject(new Error(\"Authentication timeout (5 minutes)\"));\n        }, 300000);\n\n        const checkInterval = setInterval(() => {\n          if (callbackParams) {\n            clearInterval(checkInterval);\n            clearTimeout(timeout);\n            resolve();\n          }\n        }, 100);\n      });\n\n      close();\n\n      if (callbackParams.error) {\n        throw new Error(callbackParams.error_description || callbackParams.error);\n      }\n\n      if (!callbackParams.code) {\n        throw new Error(\"No authorization code received\");\n      }\n\n      spinner.start(\"Exchanging code for tokens...\");\n\n      // Exchange code for tokens (Codex uses form-urlencoded)\n      const tokens = await this.exchangeCode(callbackParams.code, redirectUri, codeVerifier, \"application/x-www-form-urlencoded\");\n\n      spinner.text = \"Saving tokens to server...\";\n\n      // Save tokens to server\n      await this.saveTokens(tokens);\n\n      spinner.succeed(\"Codex connected successfully!\");\n      return true;","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/codex.js#L102-L138","documentation":"During the Codex (OpenAI) OAuth flow in CodexService.connect(), the local callback server (port 1455) received a redirect from OpenAI's authorization endpoint carrying error/error_description query parameters instead of an authorization code. This means the authorization server itself rejected the user's consent/login attempt, and the library re-throws the provider's message verbatim. It is thrown before any code exchange happens.","triggerScenarios":"Calling connect() (src/lib/oauth/services/codex.js:120) when the browser redirect back to http://localhost:1455/auth/callback contains error params — e.g. user clicked 'Cancel'/'Deny' on the OpenAI consent screen (access_denied), the client_id in CODEX_CONFIG is no longer valid, the redirect_uri doesn't match a registered one, or the state/PKCE parameters were rejected.","commonSituations":"Users deny the consent dialog or close it in a way that redirects back with an error; the bundled OpenAI client ID/authorize URL changed upstream; a proxy or hosts override breaks the localhost redirect; running the flow on a machine where the fixed port 1455 is used by another tool and the callback lands on the wrong server.","solutions":["Read the thrown message — it is the raw OAuth error_description (e.g. 'access_denied', 'unauthorized_client') and states exactly why the provider refused.","Re-run connect() and complete (do not cancel) the OpenAI consent screen in the browser that opens.","Verify CODEX_CONFIG (clientId, authorizeUrl, scope, extraParams) in src/lib/oauth/constants/oauth.js still matches OpenAI's current OAuth client registration.","Confirm the redirect_uri (http://localhost:1455/auth/callback) is what OpenAI expects and that nothing else is bound to port 1455."],"exampleFix":"// before: flow throws raw provider error\nif (callbackParams.error) {\n  throw new Error(callbackParams.error_description || callbackParams.error);\n}\n// after: give actionable context\nif (callbackParams.error) {\n  throw new Error(\n    `OpenAI authorization failed (${callbackParams.error}): ${callbackParams.error_description || \"no description\"}. Re-run connect() and approve the consent screen.`\n  );\n}","handlingStrategy":"validation","validationCode":"// after receiving callback params, before proceeding:\nif (callbackParams.error) {\n  console.error(\"Provider refused authorization:\", callbackParams.error, callbackParams.error_description);\n  process.exitCode = 1;\n} else if (!callbackParams.code) {\n  console.error(\"Callback missing authorization code\");\n}","typeGuard":"function hasOAuthCode(params) {\n  return !!params && typeof params === \"object\" && typeof params.code === \"string\" && params.code.length > 0 && !params.error;\n}","tryCatchPattern":"try {\n  await codexService.connect();\n} catch (err) {\n  if (/access_denied|unauthorized_client|invalid_request/.test(err.message)) {\n    console.error(\"Authorization was rejected by OpenAI. Re-run connect() and approve the consent screen.\", err.message);\n  } else throw err;\n}","preventionTips":["Always complete the consent screen; do not cancel or close the browser tab mid-flow.","Keep port 1455 free — stop other Codex-like tools before running connect().","Keep CODEX_CONFIG client registration values in sync with the current OpenAI CLI OAuth client.","Surface error_description to end users verbatim; it names the exact provider-side reason."],"tags":["oauth","authorization","codex","user-interaction"],"backgroundTag":"oauth-authorization-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}