ruvnet/ruflo · error · Error
invalid-policy-action-${name}
Error message
invalid-policy-action-${name} What it means
validateRequest() also checks the numeric action metrics: costUsd, tokens, and concurrency, when present, must be finite and non-negative — otherwise it throws Error(`invalid-policy-action-${name}`) with name substituted (invalid-policy-action-costUsd, -tokens, or -concurrency). The literal template string in the message identifies which metric is bad.
Source
Thrown at v3/@claude-flow/security/src/policy/engine.ts:281
hash,
signature: this.signingKey ? signPolicyHash(hash, this.signingKey) : undefined,
keyId: this.signingKey ? (this.keyId ?? 'local') : undefined,
};
this.state.receipts.push(receipt);
return receipt;
}
private validateRequest(request: PolicyRequest): void {
if (!request.identity?.id || !request.identity.type || !request.action?.type) {
throw new Error('invalid-policy-request');
}
for (const [name, value] of [
['costUsd', request.action.costUsd],
['tokens', request.action.tokens],
['concurrency', request.action.concurrency],
] as const) {
if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
throw new Error(`invalid-policy-action-${name}`);
}
}
}
}
export function createLegacyCompatibleState(source = 'pre-ADR-324'): PolicyState {
return {
version: POLICY_STATE_VERSION,
mode: 'legacy',
migratedFrom: source,
migratedAt: Date.now(),
rules: [],
budgets: [],
usage: [],
approvals: [],
receipts: [],
};
}View on GitHub (pinned to fa13ee4ad6)
Solutions
- Inspect the error message suffix to identify the offending metric, then fix that field's producer.
- Sanitize metrics before evaluation: omit the field when not finite/non-negative, or clamp to 0.
- Type the telemetry aggregation so undefined entries default to 0 instead of poisoning sums with NaN.
Example fix
// before
engine.evaluate({ identity, action: { type: 'llm.call', tokens: undefined as any, costUsd: total } });
// total is NaN when inputs missing -> invalid-policy-action-costUsd
// after
const costUsd = Number.isFinite(total) && total >= 0 ? total : 0;
engine.evaluate({ identity, action: { type: 'llm.call', costUsd } }); Defensive patterns
Strategy: validation
Validate before calling
function metric(v: number | undefined): number | undefined {
if (v === undefined) return undefined;
return Number.isFinite(v) && v >= 0 ? v : undefined;
}
engine.evaluate({
identity,
action: {
type: 'llm.call',
costUsd: metric(totalCost),
tokens: metric(totalTokens),
concurrency: metric(inFlight),
},
}); Type guard
function isNonNegativeFinite(v: unknown): v is number {
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
} Try / catch
try {
return engine.evaluate(request);
} catch (err) {
if (err instanceof Error && /^invalid-policy-action-(costUsd|tokens|concurrency)$/.test(err.message)) {
const field = err.message.replace('invalid-policy-action-', '');
logger.warn(`dropping bad metric ${field}=${(request.action as any)[field]}`);
return engine.evaluate({ ...request, action: { ...request.action, [field]: undefined } });
}
throw err;
} Prevention
- Sum telemetry with an explicit numeric default ((acc, v) => acc + (v ?? 0)) so undefined never poisons totals.
- Check metric names in the thrown suffix to pinpoint the field to sanitize.
- Unit-test evaluate() inputs with NaN, Infinity, and negative values for each metric.
When it happens
Trigger: action.costUsd: -0.5 from refund/credit arithmetic; tokens: NaN after summing telemetry that contains undefined; concurrency: Infinity from a division meant to compute parallelism.
Common situations: Aggregating usage numbers where one source returns undefined (sum becomes NaN); sign errors in cost accounting; passing raw provider payloads whose numeric fields are sometimes strings or missing.
Related errors
- Policy canonicalization rejects non-finite numbers
- invalid-policy-rule-limit
- invalid-budget-limit
- field "${key}" must be a finite number
- mode must be legacy, observe, or enforce
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f70a6c250f66a390.
Report an issue: GitHub.