{"record":{"id":"71f5b8eaba2e1235","repo":"can1357/oh-my-pi","slug":"refusing-to-send-official-codex-oauth-credentials","errorCode":null,"errorMessage":"Refusing to send official Codex OAuth credentials to custom endpoint ${transport.baseUrl}. Configure an API key for provider \"openai-codex\".","messagePattern":"Refusing to send official Codex OAuth credentials to custom endpoint (.+?)\\. Configure an API key for provider \"openai-codex\"\\.","errorType":"exception","errorClass":"SearchProviderError","httpStatus":null,"severity":"critical","filePath":"packages/coding-agent/src/web/search/providers/codex.ts","lineNumber":763,"sourceCode":"\t// request shape (responses-lite moves tools into an `additional_tools`\n\t// developer item), so the documented `web_search.filters.allowed_domains`\n\t// parameter cannot be assumed to survive it. Instead, re-emit directive\n\t// queries with the full Google-style operator syntax — the backing index\n\t// parses the classic operator set — and leave directive-free queries\n\t// byte-identical.\n\tconst parsed = params.parsedQuery ?? parseSearchQuery(params.query);\n\tconst query = parsed.hasDirectives ? formatQuery(parsed, GOOGLE_QUERY_SYNTAX) : params.query;\n\n\tlet result: CodexSearchResult;\n\tif (transport.customEndpoint) {\n\t\t// ModelRegistry resolves command-backed provider keys before consulting\n\t\t// its AuthStorage, so a lower-priority OAuth origin is irrelevant when\n\t\t// that command source is configured.\n\t\tconst credentialSource = params.modelRegistry?.authStorage ?? params.authStorage;\n\t\tconst credentialOrigin = credentialSource.getCredentialOrigin(\"openai-codex\");\n\t\tconst hasCommandBackedKey = params.modelRegistry?.hasCommandBackedApiKey(\"openai-codex\") === true;\n\t\tif (!hasCommandBackedKey && (credentialOrigin?.kind === \"oauth\" || credentialOrigin?.kind === \"env\")) {\n\t\t\tthrow new SearchProviderError(\n\t\t\t\t\"codex\",\n\t\t\t\t`Refusing to send official Codex OAuth credentials to custom endpoint ${transport.baseUrl}. Configure an API key for provider \"openai-codex\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst resolverOptions = {\n\t\t\tsessionId: params.sessionId,\n\t\t\tbaseUrl: transport.baseUrl,\n\t\t\tmodelId: firstCandidate.modelId,\n\t\t};\n\t\tconst keyOrResolver = params.modelRegistry\n\t\t\t? params.modelRegistry.resolver(\"openai-codex\", resolverOptions)\n\t\t\t: params.authStorage.resolver(\"openai-codex\", resolverOptions);\n\t\tresult = await withAuth(\n\t\t\tkeyOrResolver,\n\t\t\taccessToken =>\n\t\t\t\trunCodexSearchCandidates({\n\t\t\t\t\tauth: { accessToken },","sourceCodeStart":745,"sourceCodeEnd":781,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/codex.ts#L745-L781","documentation":"When a custom Codex endpoint is configured, sending official OpenAI Codex OAuth credentials (ChatGPT-account tokens) to a third-party URL would leak privileged credentials, so searchCodex throws a SearchProviderError unless an API key for provider \"openai-codex\" (or a command-backed key) is available. Command-backed keys take priority over AuthStorage origins, so they bypass this refusal.","triggerScenarios":"resolveCodexSearchTransport returns a custom endpoint (non-official baseUrl), the resolved credential origin for openai-codex is \"oauth\" or \"env\"-derived from OAuth storage, and modelRegistry.hasCommandBackedApiKey(\"openai-codex\") is false.","commonSituations":"Pointing the Codex provider at a corporate proxy or OpenAI-compatible gateway while still logged in via `omp /login openai-codex`; environment credential resolution falling back to OAuth; migration from official login to a self-hosted endpoint without issuing an API key.","solutions":["Configure a dedicated API key for provider \"openai-codex\" for use with the custom endpoint.","Set up a command-backed API key (hasCommandBackedApiKey) so the custom endpoint uses it instead of OAuth.","Remove the custom endpoint override so the official endpoint is used with OAuth credentials.","Ensure the credential origin is an API-key source rather than oauth/env if you intend to keep the custom endpoint."],"exampleFix":"// before\ncustomEndpoint = \"https://gateway.corp.internal/v1\"; // uses ChatGPT OAuth token -> refused\n// after\n// configure an API key for the gateway:\nomp /login openai-codex --api-key sk-gateway-...\n// or unset the custom endpoint to use official endpoint with OAuth","handlingStrategy":"validation","validationCode":"const origin = authStorage.getCredentialOrigin(\"openai-codex\");\nconst usingCustomEndpoint = transport.customEndpoint;\nif (usingCustomEndpoint && (origin?.kind === \"oauth\" || origin?.kind === \"env\")) {\n  throw new Error('Custom endpoint requires an API key for provider \"openai-codex\"');\n}","typeGuard":"function isOAuthToCustomEndpointRefusal(e: unknown): e is SearchProviderError {\n  return e instanceof SearchProviderError && e.message.startsWith(\"Refusing to send official Codex OAuth credentials\");\n}","tryCatchPattern":"try {\n  return await searchCodex(params);\n} catch (e) {\n  if (isOAuthToCustomEndpointRefusal(e)) {\n    // configure an API key, then retry once\n    await configureApiKey(\"openai-codex\");\n    return searchCodex(params);\n  }\n  throw e;\n}","preventionTips":["Always pair a custom endpoint with a dedicated API key, never ChatGPT OAuth tokens.","Use command-backed API keys for gateway deployments so they take credential priority.","Audit credential origins (getCredentialOrigin) in CI before enabling custom endpoints."],"tags":["security","credentials","oauth","codex","configuration"],"backgroundTag":"oauth-credential-leak-prevented","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}