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

  1. Inspect the error message suffix to identify the offending metric, then fix that field's producer.
  2. Sanitize metrics before evaluation: omit the field when not finite/non-negative, or clamp to 0.
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/f70a6c250f66a390. Report an issue: GitHub.