garrytan/gstack · error · Error

rateLimit must be >= 0

Error message

rateLimit must be >= 0

What it means

Thrown by createToken when opts.rateLimit is a negative number. The default is 10 requests per window; zero disables throttling for the token (still subject to global limits), but negative values are nonsensical and refused before the TokenInfo is built.

Source

Thrown at browse/src/token-registry.ts:209

 */
export function createToken(opts: CreateTokenOptions): TokenInfo {
  const {
    clientId,
    scopes = ['read', 'write'],
    domains,
    tabPolicy = 'own-only',
    rateLimit = 10,
    expiresSeconds = 86400, // 24h default
  } = opts;

  // Validate inputs
  const validScopes: ScopeCategory[] = ['read', 'write', 'admin', 'meta', 'control'];
  for (const s of scopes) {
    if (!validScopes.includes(s as ScopeCategory)) {
      throw new Error(`Invalid scope: ${s}. Valid: ${validScopes.join(', ')}`);
    }
  }
  if (rateLimit < 0) throw new Error('rateLimit must be >= 0');
  if (expiresSeconds !== null && expiresSeconds !== undefined && expiresSeconds < 0) {
    throw new Error('expiresSeconds must be >= 0 or null');
  }

  const token = generateToken('gsk_sess_');
  const now = new Date();
  const expiresAt = expiresSeconds === null
    ? null
    : new Date(now.getTime() + expiresSeconds * 1000).toISOString();

  const info: TokenInfo = {
    token,
    clientId,
    type: 'session',
    scopes,
    domains,
    tabPolicy,
    rateLimit,

View on GitHub (pinned to 94993f7401)

Solutions

  1. Use 0 to disable per-token rate limiting, or a positive integer for requests/window.
  2. Read env vars with a guarded parser: `const rl = Number(process.env.RL ?? 10); if (!Number.isFinite(rl) || rl < 0) throw ...`.
  3. If your legacy config used -1 for unlimited, map it to 0 at the boundary before calling createToken.

Example fix

// before
createToken({ clientId: 'bot', rateLimit: -1 }); // 'unlimited' intent
// after
createToken({ clientId: 'bot', rateLimit: 0 }); // 0 = no per-token cap
Defensive patterns

Strategy: validation

Validate before calling

function parseRateLimit(v: unknown): number {
  const n = typeof v === 'string' ? Number(v) : v;
  if (typeof n !== 'number' || !Number.isFinite(n) || n < 0) {
    throw new Error('rateLimit must be >= 0');
  }
  return Math.floor(n);
}

Type guard

const isNonNegativeInt = (v: unknown): v is number =>
  typeof v === 'number' && Number.isFinite(v) && v >= 0 && Number.isInteger(v);

Try / catch

try {
  return createToken({ clientId, rateLimit });
} catch (e: any) {
  if (/rateLimit must be >= 0/.test(e.message)) {
    return createToken({ clientId, rateLimit: 0 }); // 0 = no per-token cap
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing rateLimit: -1 explicitly; computing the limit from an env var that defaults to -1 on missing config; arithmetic that subtracts where it should add.

Common situations: Misreading the option as a 'grace period' or 'buffer'; migrating from a config schema that used -1 to mean 'unlimited' (this library uses null or 0 instead); env var coercion turning an empty string into NaN (which actually slips through — but a literal -1 hits this).

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/836beab3df34fed7. Report an issue: GitHub.