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
- Re-run OAuth for the account to obtain a fresh token set including refresh_token
- Store the access token expiry on the account before exporting
- Skip accounts missing both refresh_token and expiry in the export batch
- 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
- Persist token expiry metadata on every account
- Prefer OAuth flows that yield refresh tokens
- Pre-flight batch exports for refresh_token or expiry presence
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
- SUB2API_API_KEY_MISSING
- Official auth.json requires OPENAI_API_KEY for API Key accou
- invalidJsonMessage
- messages.rawLineNoRefreshToken(item.lineNumber)
- messages.rawLineMultipleRefreshTokens(item.lineNumber)
AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05).
Data as JSON: /api/errors/f91a3c25b3cbf0d4.
Report an issue: GitHub.