{"record":{"id":"db37fac4d2eac144","repo":"coleam00/Archon","slug":"openai-token-operation-response-did-not-include","errorCode":null,"errorMessage":"OpenAI token ${operation} response did not include an id_token (required by the Codex CLI).","messagePattern":"OpenAI token (.+?) response did not include an id_token \\(required by the Codex CLI\\)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/credentials/openai-oauth.ts","lineNumber":244,"sourceCode":"  operation: 'exchange' | 'refresh',\n  previous?: OAuthCredentials\n): OpenAiOAuthCredentials {\n  const access = typeof json.access_token === 'string' ? json.access_token : '';\n  const expiresIn = typeof json.expires_in === 'number' ? json.expires_in : NaN;\n  if (!access || !Number.isFinite(expiresIn)) {\n    throw new Error(`OpenAI token ${operation} response missing access_token/expires_in.`);\n  }\n  const prevRefresh = typeof previous?.refresh === 'string' ? previous.refresh : '';\n  const refresh = typeof json.refresh_token === 'string' ? json.refresh_token : prevRefresh;\n  if (!refresh) {\n    throw new Error(`OpenAI token ${operation} response missing refresh_token.`);\n  }\n  const prevIdToken = typeof previous?.id_token === 'string' ? previous.id_token : '';\n  const idToken = typeof json.id_token === 'string' && json.id_token ? json.id_token : prevIdToken;\n  if (!idToken) {\n    // Fail loud: an id_token-less credential reproduces the exact #1924\n    // breakage (\"invalid ID token format\" in the Codex CLI) — never store one.\n    throw new Error(\n      `OpenAI token ${operation} response did not include an id_token (required by the Codex CLI).`\n    );\n  }\n  const prevAccountId = typeof previous?.accountId === 'string' ? previous.accountId : '';\n  const accountId = accountIdFromAccessToken(access) ?? prevAccountId;\n  if (!accountId) {\n    throw new Error('Failed to extract the ChatGPT account id from the OpenAI access token.');\n  }\n  return {\n    // Preserve any extra fields a future token response taught us to keep.\n    ...(previous ?? {}),\n    access,\n    refresh,\n    expires: Date.now() + expiresIn * 1000,\n    accountId,\n    id_token: idToken,\n  };\n}","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/credentials/openai-oauth.ts#L226-L262","documentation":"credentialsFromTokenResponse demands an id_token in the token response because the Codex CLI requires it — a credential stored without one reproduces GitHub issue #1924 ('invalid ID token format'). It uses json.id_token, falling back to previous.id_token, and throws when neither exists. This is an intentional fail-loud guard against persisting an unusable credential.","triggerScenarios":"The OpenAI token exchange/refresh response contains no id_token field (or an empty string), and the previous credential also has no id_token.","commonSituations":"Calling the token endpoint with grant types or scopes that skip ID-token issuance; a custom/proxied token endpoint that strips id_token; OpenAI-side changes; building credentials from a partial response saved earlier that already lacked id_token.","solutions":["Redo the full OAuth authorization-code flow (PKCE login) so OpenAI issues a fresh id_token.","Verify the token request goes to the official https://auth.openai.com/oauth/token, not a proxy that strips fields.","Pass the previous credential through so its stored id_token is reused during refresh.","Ensure the client config uses OpenAI's documented scopes/audience so an OIDC id_token is issued."],"exampleFix":"// before: constructing credentials without id_token\nconst creds = credentialsFromTokenResponse(json, 'exchange'); // throws if response lacks id_token\n// after: fall back to the previously stored credential\nconst creds = credentialsFromTokenResponse(json, 'exchange', storedCreds); // reuse stored id_token","handlingStrategy":"validation","validationCode":"const hasIdToken = (j, prev) =>\n  (typeof j?.id_token === 'string' && j.id_token) || (typeof prev?.id_token === 'string' && prev.id_token);\nif (!hasIdToken(json, storedCreds)) throw new Error('token response lacks id_token; redo PKCE login');","typeGuard":"function hasIdToken(c: { id_token?: unknown }): c is { id_token: string } {\n  return typeof c.id_token === 'string' && c.id_token.length > 0;\n}","tryCatchPattern":"try {\n  creds = credentialsFromTokenResponse(json, op, previous);\n} catch (e) {\n  if (e.message.includes('did not include an id_token')) {\n    creds = await runOpenAiLoginFlow(); // fresh login re-issues id_token\n  } else throw e;\n}","preventionTips":["Use the official https://auth.openai.com/oauth/token endpoint — proxies can strip id_token.","Keep the full stored credential (including id_token) across refreshes so the fallback applies.","After a provider/OIDC config change, do a fresh login instead of refreshing old tokens.","Test the credential against the Codex CLI early — it is the consumer that fails without id_token."],"tags":["oauth","openai","id-token","codex","auth"],"backgroundTag":"oauth-id-token-missing","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}