coleam00/Archon · error

Missing authorization code.

Error message

Missing authorization code.

What it means

Thrown by runOpenAiManualLogin when the pasted OpenAI authorization input parses successfully but contains no authorization `code` parameter. Without the code the PKCE token exchange cannot proceed, so the manual login fails explicitly rather than exchanging nothing.

Solutions

  1. Complete the OAuth consent in the browser and copy the full final redirect URL including ?code=...
  2. If the browser showed an authorization error (e.g. access denied), restart the login and approve the request.
  3. Paste the entire URL — don't trim off query parameters.
  4. Retry the login if the flow expired; a fresh attempt issues a fresh verifier and code.

Example fix

// before (truncated, no code)
https://auth.openai.com/oauth/callback?state=abc
// after (complete redirect)
https://auth.openai.com/oauth/callback?state=abc&code=xyz
Defensive patterns

Strategy: validation

Validate before calling

import { parseOpenAiAuthorizationInput } from './openai-oauth';
function hasAuthCode(input: string): boolean {
  return !!parseOpenAiAuthorizationInput(input).code;
}

Try / catch

try {
  const creds = await loginPromise;
} catch (e) {
  if ((e as Error).message === 'Missing authorization code.') {
    // retry the login; paste the complete final redirect URL with ?code=
  } else throw e;
}

Prevention

When it happens

Trigger: Awaiting session.codeDeferred and receiving input where parseOpenAiAuthorizationInput finds no code: pasting the bare authorization page URL without query params, pasting only the state parameter, pasting an error redirect (?error=access_denied), or pasting unrelated text/an empty line.

Common situations: User copies the callback URL before completing consent (no code yet); authorization was denied so the redirect carries an error instead of a code; pasting the wrong URL entirely; truncating the URL and losing the code query parameter.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/88be39c45db22ce3. Report an issue: GitHub.

Appendix: source

Thrown at packages/core/src/credentials/oauth-bridge.ts:222

 * URL (PKCE), surface it on the session, wait for the pasted redirect URL /
 * code via the same `codeDeferred` the Pi manual flows use (so poll(code) and
 * abort semantics are identical), then exchange it directly — capturing the
 * `id_token` Pi drops. Runs NO local callback server (the #1963 wedge
 * pattern); the user pastes the final redirect URL or code back instead.
 */
async function runOpenAiManualLogin(session: OAuthSession): Promise<OpenAiOAuthCredentials> {
  const flow = createOpenAiAuthorizeFlow();
  session.url = flow.url;
  if (session.mode === 'pending') session.mode = 'manual';
  session.firstSignal.resolve(true);
  // Rejected by abortSession on cancel/supersede/expiry — same as Pi flows.
  const input = await session.codeDeferred.promise;
  const parsed = parseOpenAiAuthorizationInput(input);
  if (parsed.state && parsed.state !== flow.state) {
    throw new Error('OAuth state mismatch.');
  }
  if (!parsed.code) {
    throw new Error('Missing authorization code.');
  }
  // Returns its true type — the loginPromise join is typed as delivery.ts's
  // loose `OAuthCredentials`, which this satisfies structurally (no cast).
  return exchangeOpenAiAuthorizationCode(parsed.code, flow.verifier, session.abort.signal);
}

/**
 * Begin a subscription login for a vendor (anthropic/openai/github-copilot;
 * legacy claude/codex/copilot ids accepted). Kicks off the held login —
 * Pi's `login()` for anthropic/github-copilot, the Archon-owned PKCE flow for
 * openai — and returns once the first signal has populated the URL (manual)
 * or user-code (device), or a short timeout elapses.
 */
export async function startOAuth(userId: string, providerId: string): Promise<StartOAuthResult> {
  // Expired sessions may also hold a callback server — include them in the
  // settle-wait below so the port is free before the new login binds it.
  const supersededSettled: Promise<void>[] = sweepExpired();
  const provider = normalizeCredentialVendor(providerId);

View on GitHub (pinned to 0773b97458)