coleam00/Archon · error

Stored OpenAI credential has no refresh token.

Error message

Stored OpenAI credential has no refresh token.

What it means

refreshOpenAiOAuthCredentials reads the refresh token from the stored credential before contacting OpenAI. If the stored OAuthCredentials has no string `refresh` field, it throws immediately instead of sending a doomed request — a credential without a refresh token can never be refreshed and must be re-created via a fresh login.

Source

Thrown at packages/core/src/credentials/openai-oauth.ts:299

    }),
    'exchange',
    signal
  );
  return credentialsFromTokenResponse(json, 'exchange');
}

/**
 * Refresh an OpenAI subscription credential directly (same public client id).
 * Preserves `id_token` (and `refresh`) when the refresh response omits them —
 * the reason this does NOT go through Pi's `getOAuthApiKey`, which would
 * rebuild the blob from scratch and drop the id_token on every rotation.
 */
export async function refreshOpenAiOAuthCredentials(
  creds: OAuthCredentials
): Promise<OpenAiOAuthCredentials> {
  const refresh = typeof creds.refresh === 'string' ? creds.refresh : '';
  if (!refresh) {
    throw new Error('Stored OpenAI credential has no refresh token.');
  }
  const json = await postTokenRequest(
    new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: OPENAI_CLIENT_ID,
      refresh_token: refresh,
    }),
    'refresh'
  );
  return credentialsFromTokenResponse(json, 'refresh', creds);
}

/**
 * Mint a usable bearer from a stored OpenAI credential blob, refreshing first
 * when expired. Same contract as Pi's `getOAuthApiKey` (`{ newCredentials,
 * apiKey } | null`) so the store's shared rotation/resave logic applies
 * unchanged. Throws when a needed refresh fails.
 *

View on GitHub (pinned to 0773b97458)

Solutions

  1. Force a fresh OAuth login (authorization-code + PKCE) to obtain a new credential with a refresh token.
  2. Inspect the stored credential row/JSON to confirm the refresh field is present and non-empty; re-import if truncated.
  3. Check the code path that saved the credential — earlier versions or error paths may have omitted refresh_token.
  4. If the credential was migrated, backfill the refresh token from the original login response or re-authenticate.

Example fix

// before
const fresh = await refreshOpenAiOAuthCredentials({ access: token }); // throws: no refresh
// after: guard and re-authenticate when refresh is absent
if (typeof creds.refresh !== 'string' || !creds.refresh) {
  creds = await runOpenAiLoginFlow();
}
const fresh = await refreshOpenAiOAuthCredentials(creds);
Defensive patterns

Strategy: type-guard

Validate before calling

function canRefresh(c) { return c && typeof c.refresh === 'string' && c.refresh.length > 0; }
if (!canRefresh(creds)) throw new Error('stored OpenAI credential lacks refresh token; re-authentication required');

Type guard

function isOpenAiRefreshable(c: OAuthCredentials): c is OAuthCredentials & { refresh: string } {
  return typeof c.refresh === 'string' && c.refresh.length > 0;
}

Try / catch

try {
  return await refreshOpenAiOAuthCredentials(creds);
} catch (e) {
  if (e.message === 'Stored OpenAI credential has no refresh token.') {
    return await promptFreshOpenAiLogin(); // user-visible re-auth
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling refreshOpenAiOAuthCredentials (directly or via next()/mintOpenAiOAuthApiKey) with a credential whose `refresh` property is undefined, non-string, or empty.

Common situations: Credential loaded from an old database row created before refresh tokens were stored; credentials constructed by hand from just an access token; partial deserialization of stored JSON that dropped the refresh field.

Related errors


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