ruvnet/ruflo · error · Error

invalid-policy-rule-limit

Error message

invalid-policy-rule-limit

What it means

When a rule defines optional numeric constraints (maxCostUsd, maxTokens, maxConcurrency), upsertRule() checks each: a value that is present but non-finite or negative throws Error('invalid-policy-rule-limit'). Limits are caps, so NaN, Infinity, and negative numbers are all meaningless and rejected.

Source

Thrown at v3/@claude-flow/security/src/policy/engine.ts:86

  setMode(mode: PolicyState['mode']): void {
    this.state.mode = mode;
  }

  setConfiguredMode(mode: PolicyState['mode']): void {
    this.state.configuredMode = mode;
    const rank = { legacy: 0, observe: 1, enforce: 2 } as const;
    if (rank[mode] > rank[this.state.mode]) this.state.mode = mode;
  }

  upsertRule(rule: PolicyRule): void {
    if (!rule.id || !rule.actions.length) throw new Error('invalid-policy-rule');
    for (const value of [
      rule.constraints?.maxCostUsd,
      rule.constraints?.maxTokens,
      rule.constraints?.maxConcurrency,
    ]) {
      if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
        throw new Error('invalid-policy-rule-limit');
      }
    }
    const index = this.state.rules.findIndex((item) => item.id === rule.id);
    if (index >= 0) this.state.rules[index] = structuredClone(rule);
    else this.state.rules.push(structuredClone(rule));
  }

  setBudget(limit: BudgetLimit): void {
    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');
    if (!Number.isFinite(limit.periodMs)
      || [limit.maxCostUsd, limit.maxTokens].some((value) => (
        value !== undefined && (!Number.isFinite(value) || value < 0)
      ))) throw new Error('invalid-budget-limit');
    const index = this.state.budgets.findIndex((item) => item.id === limit.id);
    if (index >= 0) this.state.budgets[index] = structuredClone(limit);
    else this.state.budgets.push(structuredClone(limit));
  }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Use undefined (omit the key) to express 'no limit' — the check only applies when the value is defined.
  2. Guard numeric env parsing: only assign when Number.isFinite(v) && v >= 0.
  3. Correct negative values to zero or a positive cap depending on intent.

Example fix

// before
const maxTokens = Number(env.MAX_TOKENS); // NaN when unset -> throws
engine.upsertRule({ id: 'r', actions: ['run'], constraints: { maxTokens } });

// after
const parsed = Number(env.MAX_TOKENS);
const maxTokens = Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
engine.upsertRule({ id: 'r', actions: ['run'], constraints: { maxTokens } });
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeLimit(v: number | undefined): number | undefined {
  if (v === undefined) return undefined;
  if (!Number.isFinite(v) || v < 0) throw new Error(`invalid limit: ${v}`);
  return v;
}
engine.upsertRule({
  id: 'r',
  actions: ['run'],
  constraints: {
    maxCostUsd: sanitizeLimit(parsedCost),
    maxTokens: sanitizeLimit(parsedTokens),
    maxConcurrency: sanitizeLimit(parsedConcurrency),
  },
});

Type guard

function isValidLimit(v: unknown): v is number {
  return v === undefined || (typeof v === 'number' && Number.isFinite(v) && v >= 0);
}

Try / catch

try {
  engine.upsertRule(rule);
} catch (err) {
  if (err instanceof Error && err.message === 'invalid-policy-rule-limit') {
    throw new ConfigError(`rule ${rule.id} has a non-finite or negative constraint`);
  }
  throw err;
}

Prevention

When it happens

Trigger: constraints: { maxCostUsd: -1 } from a sign typo; { maxTokens: NaN } from parsing 'unlimited' or an empty env var; { maxConcurrency: Infinity } used to mean 'no cap'.

Common situations: Env-driven config where an unset variable parses to NaN; using -1 as a 'disabled' sentinel (common in other systems) which this engine rejects; spreadsheet-edited policy files with negative values.

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 ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/e588b4c45b3d4116. Report an issue: GitHub.