jlcodes99/cockpit-tools · error

SUB2API_ACCESS_TOKEN_EXPIRY_MISSING

SUB2API_ACCESS_TOKEN_EXPIRY_MISSING

Error message

SUB2API_ACCESS_TOKEN_EXPIRY_MISSING

What it means

toSub2apiAccount throws SUB2API_ACCESS_TOKEN_EXPIRY_MISSING when an OAuth account has no refresh_token and the access token expiry (resolveAccessTokenExpiry) cannot be determined. Sub2api requires either refresh_token or an expires_at timestamp to manage token lifecycle, so the export aborts.

Source

Thrown at src/utils/codexExportFormats.ts:417

    };
  }
  if (!account.tokens.access_token?.trim()) {
    throw new Error('SUB2API_ACCESS_TOKEN_MISSING');
  }

  const credentials = buildSub2apiCredentials(account);
  const extra = buildSub2apiExtra(account);
  const item: Sub2apiCreateAccountItem = {
    ...base,
    type: 'oauth',
    credentials,
    ...(extra ? { extra } : {}),
  };

  if (!account.tokens.refresh_token?.trim()) {
    const tokenExpiresAt = resolveAccessTokenExpiry(account);
    if (!tokenExpiresAt) {
      throw new Error('SUB2API_ACCESS_TOKEN_EXPIRY_MISSING');
    }
    item.expires_at = Math.floor(new Date(tokenExpiresAt).getTime() / 1000);
    item.auto_pause_on_expired = true;
  }

  return item;
}

function toPortableTokenStorage(
  account: CodexAccount,
  options: CodexExportBuildOptions = {},
): CodexPortableTokenStorage {
  const payload: CodexPortableTokenStorage = {
    id_token: account.tokens.id_token || '',
    access_token: account.tokens.access_token || '',
    refresh_token: account.tokens.refresh_token?.trim() || '',
    account_id: resolveAccountId(account) || '',
    last_refresh: resolveLastRefresh(account),

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Re-run OAuth for the account to obtain a fresh token set including refresh_token
  2. Store the access token expiry on the account before exporting
  3. Skip accounts missing both refresh_token and expiry in the export batch
  4. Verify resolveAccessTokenExpiry covers all account variants your data contains

Example fix

// before
const item = toSub2apiAccount(account); // throws SUB2API_ACCESS_TOKEN_EXPIRY_MISSING
// after
if (account.tokens?.refresh_token?.trim() || resolveAccessTokenExpiry(account)) {
  const item = toSub2apiAccount(account);
}
Defensive patterns

Strategy: validation

Validate before calling

const canExport = !!account.tokens?.refresh_token?.trim() || !!resolveAccessTokenExpiry(account);
if (!canExport) throw new Error('cannot export: no refresh_token and no expiry');

Type guard

function hasTokenLifecycle(a: CodexAccount): boolean {
  return !!a.tokens?.refresh_token?.trim() || !!resolveAccessTokenExpiry(a);
}

Try / catch

try {
  const item = toSub2apiAccount(account);
} catch (e) {
  if ((e as Error).message === 'SUB2API_ACCESS_TOKEN_EXPIRY_MISSING') {
    // re-auth to obtain refresh_token or record expiry
  }
}

Prevention

When it happens

Trigger: Calling toSub2apiAccount for an OAuth account with empty tokens.refresh_token where resolveAccessTokenExpiry(account) returns null/undefined (no expiry stored anywhere in the account data).

Common situations: Imported account data lacking both refresh_token and expiry metadata; hand-edited account records; older account format that did not persist token expiry.

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/f91a3c25b3cbf0d4. Report an issue: GitHub.