can1357/oh-my-pi · error · AIError.OAuthError

Failed to extract accountId from token

Error message

Failed to extract accountId from token

What it means

Thrown after a successful token exchange when the decoded/required fields are present but the ID/access token does not yield an accountId (no chatgpt account_id claim). The library needs the ChatGPT account identifier to construct Codex sessions, so a token without it is unusable and is rejected with kind='validation'.

Source

Thrown at packages/ai/src/registry/oauth/openai-codex.ts:212

			`Token exchange failed: ${formatOpenAICodexTokenEndpointError(tokenResponse.status, bodyText)}`,
			{ kind: "token-exchange", status: tokenResponse.status },
		);
	}

	const tokenData = (await tokenResponse.json()) as {
		access_token?: string;
		refresh_token?: string;
		id_token?: string;
		expires_in?: number;
	};

	if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") {
		throw new AIError.OAuthError("Token response missing required fields", { kind: "validation" });
	}

	const { accountId, email, planType } = getTokenProfile(tokenData.access_token, tokenData.id_token);
	if (!accountId) {
		throw new AIError.OAuthError("Failed to extract accountId from token", { kind: "validation" });
	}

	return {
		access: tokenData.access_token,
		refresh: tokenData.refresh_token,
		expires: Date.now() + tokenData.expires_in * 1000,
		accountId,
		email,
		orgId: accountId,
		orgName: planType,
	};
}

/**
 * Login with OpenAI Codex OAuth
 */
export type OpenAICodexLoginOptions = OAuthController & {
	/** Optional originator value for OpenAI Codex OAuth. Default matches OMP Codex request headers. */

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure you log in with a ChatGPT account that has Codex/plus access, not an API-only account
  2. Update the CLI — newer versions track current OpenAI token claim shapes
  3. Try switching accounts at auth.openai.com and redoing the device login
  4. Inspect your access token's claims (jwt.io) to confirm whether chatgpt_account_id is present

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

// pre-check the account type you will authorize with: it must be a ChatGPT account
// with Codex access; API-only accounts lack chatgpt_account_id claims.
const claims = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString());
if (!claims.chatgpt_account_id) throw new Error('Account has no ChatGPT account_id — use a ChatGPT-enabled account');

Type guard

function hasAccountId(d: { accountId?: string | null }): d is { accountId: string } {
  return typeof d.accountId === 'string' && d.accountId.length > 0;
}

Try / catch

try {
  const tokens = await exchangeCodeForToken(code, verifier);
} catch (e) {
  if (e instanceof AIError.OAuthError && e.message === 'Failed to extract accountId from token') {
    console.error('Signed in with an account lacking ChatGPT/Codex claims. Re-login with a ChatGPT account.');
  } else throw e;
}

Prevention

When it happens

Trigger: getTokenProfile() parses the access token / id_token JWT claims and accountId comes back falsy — the token was issued without ChatGPT account claims (wrong auth audience, API-key-style token, non-ChatGPT OpenAI account type).

Common situations: Account is an org-only or API-only OpenAI account without a ChatGPT subscription; OpenAI changed the JWT claim shape and the CLI predates it; user authorized with a different account type than expected (e.g. team member without Codex access).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b56c22132d1874f8. Report an issue: GitHub.