{"record":{"id":"5497320ffad62ef7","repo":"tinyhumansai/openhuman","slug":"openai-codex-oauth-missing-callback-url","errorCode":"OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL","errorMessage":"OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL","messagePattern":"OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/services/api/aiSettingsApi.ts","lineNumber":734,"sourceCode":"  await authRemoveProviderCredentials({ provider: authKeyForSlug(slug), profile: 'default' });\n}\n\nexport async function startOpenAiCodexOAuth(): Promise<OpenAiCodexOAuthStartResult> {\n  const res = await callCoreRpc<{ result: OpenAiCodexOAuthStartResult }>({\n    method: 'openhuman.inference_openai_oauth_start',\n    params: {},\n  });\n  const authUrl = res?.result?.authUrl?.trim();\n  if (!authUrl) {\n    throw new Error(OPENAI_CODEX_OAUTH_MISSING_AUTH_URL);\n  }\n  return res.result;\n}\n\nexport async function completeOpenAiCodexOAuth(callbackUrl: string): Promise<void> {\n  const callback = callbackUrl.trim();\n  if (!callback) {\n    throw new Error(OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL);\n  }\n  await callCoreRpc({\n    method: 'openhuman.inference_openai_oauth_complete',\n    params: { callback_url: callback },\n  });\n}\n\nexport async function importOpenAiCodexCliAuth(): Promise<void> {\n  await callCoreRpc({ method: 'openhuman.inference_openai_oauth_import_codex_cli', params: {} });\n}\n\n/**\n * Eagerly write the cloud_providers list to the core config.\n *\n * Called immediately when providers are added/edited/removed so that\n * `listProviderModels` can resolve the provider by id without waiting for\n * the user to click the global Save button.  API keys are NOT included here\n * (they're written via `setCloudProviderKey` on their own path).","sourceCodeStart":716,"sourceCodeEnd":752,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/api/aiSettingsApi.ts#L716-L752","documentation":"Thrown by completeOpenAiCodexOAuth() before any RPC is sent, when the callbackUrl argument is empty or only whitespace after trimming. The function expects the full redirect URL (containing the OAuth code/state) captured from the browser or deep link after the user authorizes; passing anything blank cannot be completed. This is pure input validation — the core is never contacted.","triggerScenarios":"Calling completeOpenAiCodexOAuth(''), completeOpenAiCodexOAuth('   '), or completeOpenAiCodexOAuth(undefined as any); a deep-link handler extracting the callback URL from a URL fragment/query that is absent or misnamed, yielding an empty string.","commonSituations":"Deep-link registration missing or the OS delivered a bare openhuman:// host without query params; the OAuth redirect_uri mismatch so the app opens without code/state; a refactor changed where the callback URL is read from (window.location.href vs event payload) and it is now undefined coerced to ''.string.","solutions":["Log/inspect the callbackUrl at the call site — it must be the complete redirect URL including ?code=...&state=... before calling completeOpenAiCodexOAuth.","Fix the extraction: for deep links read the URL from the Tauri deep-link event payload, not window.location, and check the param name the core's redirect uses.","Verify the OAuth start's redirect URI matches what is registered so the callback actually carries code/state.","Guard the UI flow: only call complete when a non-empty callback exists; otherwise restart the OAuth flow with startOpenAiCodexOAuth()."],"exampleFix":"// before\nexport async function completeOpenAiCodexOAuth(callbackUrl: string): Promise<void> {\n  const callback = callbackUrl.trim();\n  if (!callback) {\n    throw new Error(OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL);\n  }\n  await callCoreRpc({ method: 'openhuman.inference_openai_oauth_complete', params: { callback_url: callback } });\n}\n\n// after — caller validates and reuses the OAuth start value\nconst callback = extractCallbackUrl(deepLinkEvent); // may be ''\nif (!callback) {\n  await startOpenAiCodexOAuth(); // restart the flow instead of throwing\n} else {\n  await completeOpenAiCodexOAuth(callback);\n}","handlingStrategy":"validation","validationCode":"const callback = typeof deepLinkUrl === 'string' ? deepLinkUrl.trim() : '';\nif (!callback || !callback.includes('code=')) {\n  // do not call completeOpenAiCodexOAuth — restart the flow instead\n  await startOpenAiCodexOAuth();\n} else {\n  await completeOpenAiCodexOAuth(callback);\n}","typeGuard":"function isNonEmptyCallbackUrl(url: unknown): url is string {\n  return typeof url === 'string' && url.trim().length > 0 && /^https?:\\/\\/|^[a-z]+:\\/\\//i.test(url.trim());\n}","tryCatchPattern":"try {\n  await completeOpenAiCodexOAuth(callback);\n} catch (e) {\n  if (e instanceof Error && e.message === 'OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL') {\n    // restart OAuth instead of surfacing an internal validation error\n    await startOpenAiCodexOAuth();\n  } else throw e;\n}","preventionTips":["Extract the deep-link URL from the Tauri deep-link event payload, not window.location.","Validate the callback contains code/state before completing.","Type deep-link handlers as string | undefined and normalize early."],"tags":["oauth","input-validation","deep-link","codex"],"backgroundTag":"missing-required-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}