actualbudget/actual · error

Invalid token_expiration value: ${val}: value was "${val}"

Error message

Invalid token_expiration value: ${val}: value was "${val}"

What it means

Thrown by the token_expiration validator in load-config.js during server startup. The value must be 'never', 'openid-provider', a finite non-negative number, or a string coercible to such a number; anything else (e.g. '30d', 'abc', negative numbers) aborts config loading with this message.

Source

Thrown at packages/sync-server/src/load-config.js:43

  path.dirname(require.resolve('@actual-app/web/package.json')),
  'build',
);
debug(`Actual web build path: '${actualAppWebBuildPath}'`);

// Custom formats
convict.addFormat({
  name: 'tokenExpiration',
  validate(val) {
    if (val === 'never' || val === 'openid-provider') return;
    if (typeof val === 'number' && Number.isFinite(val) && val >= 0) return;

    // Handle string values that can be converted to numbers (from env vars)
    if (typeof val === 'string') {
      const numVal = Number(val);
      if (Number.isFinite(numVal) && numVal >= 0) return;
    }

    throw new Error(
      `Invalid token_expiration value: ${val}: value was "${val}"`,
    );
  },
  coerce(val) {
    if (val === 'never' || val === 'openid-provider') return val;
    if (typeof val === 'number') return val;

    // Convert string values to numbers for environment variables
    if (typeof val === 'string') {
      const numVal = Number(val);
      if (Number.isFinite(numVal) && numVal >= 0) return numVal;
    }

    return val; // Let validate() handle invalid values
  },
});

// Main config schema

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Set TOKEN_EXPIRATION to a plain number of days (e.g. '60') — only integers/floats >= 0 are accepted.
  2. Use the special literals 'never' or 'openid-provider' if you want no expiry / provider-controlled expiry.
  3. Convert duration expressions yourself: '30d' -> '30'.
  4. Check for stray quotes, spaces, or CR characters in the env value if a numeric-looking value still fails.

Example fix

// before
TOKEN_EXPIRATION=30d      // throws 'Invalid token_expiration value: 30d'
// after
TOKEN_EXPIRATION=30
Defensive patterns

Strategy: validation

Validate before calling

const raw = process.env.TOKEN_EXPIRATION;
const isValid = raw === undefined || raw === 'never' || raw === 'openid-provider' ||
  (Number.isFinite(Number(raw)) && Number(raw) >= 0);
if (!isValid) throw new Error(`TOKEN_EXPIRATION="${raw}" is invalid; use a non-negative number, 'never', or 'openid-provider'`);

Type guard

function isTokenExpiration(v: unknown): v is 'never' | 'openid-provider' | number {
  if (v === 'never' || v === 'openid-provider') return true;
  if (typeof v === 'number') return Number.isFinite(v) && v >= 0;
  if (typeof v === 'string') { const n = Number(v); return Number.isFinite(n) && n >= 0; }
  return false;
}

Try / catch

try {
  const config = loadConfig(configDir);
  startServer(config);
} catch (err) {
  if (err instanceof Error && err.message.includes('Invalid token_expiration')) {
    console.error('Fix TOKEN_EXPIRATION env var: number of days, or never/openid-provider');
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Setting TOKEN_EXPIRATION env var to a non-numeric string like '30d', '-1', '1h', or leaving whitespace/garbage in the env; putting an invalid default in the config file.

Common situations: Admins assuming human-readable durations ('24h', '7d') are supported; copying Docker env examples with placeholder text; typos like 'never ' with trailing space handled, but 'expired' etc. not.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/5b0c0fa32c847f04. Report an issue: GitHub.