tinyhumansai/openhuman · error

OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL

OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL

Error message

OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL

What it means

completeOpenAiCodexOAuth requires the OAuth callback URL (the deep link the browser redirected back to, containing the code/state query). After trimming, an empty value cannot be exchanged for tokens, so the client rejects it before calling openhuman.inference_openai_oauth_complete.

Source

Thrown at app/src/services/api/aiSettingsApi.ts:734

  await authRemoveProviderCredentials({ provider: authKeyForSlug(slug), profile: 'default' });
}

export async function startOpenAiCodexOAuth(): Promise<OpenAiCodexOAuthStartResult> {
  const res = await callCoreRpc<{ result: OpenAiCodexOAuthStartResult }>({
    method: 'openhuman.inference_openai_oauth_start',
    params: {},
  });
  const authUrl = res?.result?.authUrl?.trim();
  if (!authUrl) {
    throw new Error(OPENAI_CODEX_OAUTH_MISSING_AUTH_URL);
  }
  return res.result;
}

export async function completeOpenAiCodexOAuth(callbackUrl: string): Promise<void> {
  const callback = callbackUrl.trim();
  if (!callback) {
    throw new Error(OPENAI_CODEX_OAUTH_MISSING_CALLBACK_URL);
  }
  await callCoreRpc({
    method: 'openhuman.inference_openai_oauth_complete',
    params: { callback_url: callback },
  });
}

export async function importOpenAiCodexCliAuth(): Promise<void> {
  await callCoreRpc({ method: 'openhuman.inference_openai_oauth_import_codex_cli', params: {} });
}

/**
 * Eagerly write the cloud_providers list to the core config.
 *
 * Called immediately when providers are added/edited/removed so that
 * `listProviderModels` can resolve the provider by id without waiting for
 * the user to click the global Save button.  API keys are NOT included here
 * (they're written via `setCloudProviderKey` on their own path).

View on GitHub (pinned to a221052e0d)

Solutions

  1. Capture the full callback URL from the deep-link event and pass it through unmodified
  2. Guard the handler: if the trimmed URL is empty, ignore the event (it may be an unrelated scheme invocation)
  3. Verify the redirect URI configured in the OAuth app matches the deep link so the full URL survives

Example fix

// before
onDeepLink(url => completeOpenAiCodexOAuth(extractPath(url)));

// after
onDeepLink(url => {
  const callback = url?.trim();
  if (!callback) return; // unrelated or incomplete invocation
  completeOpenAiCodexOAuth(callback).catch(notify);
});
Defensive patterns

Strategy: validation

Validate before calling

const callback = typeof callbackUrl === 'string' ? callbackUrl.trim() : '';
if (callback) await completeOpenAiCodexOAuth(callback);

Type guard

const isNonEmptyTrimmed = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;

Try / catch

try { await completeOpenAiCodexOAuth(callbackUrl); }
catch (e) { if (String(e.message).includes('callback')) restartOAuthFlow(); else throw e; }

Prevention

When it happens

Trigger: Calling completeOpenAiCodexOAuth('') or completeOpenAiCodexOAuth(' ') — e.g. a deep-link handler that fires on the bare scheme openhuman://oauth without the path/query, or a race where the handler runs before the URL is captured.

Common situations: Deep-link registration handling only part of the URL; the callback arriving with query params stripped by a browser/launcher; manual testing that calls the completer without a real redirect; string parsing that extracts the wrong segment and yields empty.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/5497320ffad62ef7. Report an issue: GitHub.