ruvnet/ruflo · error · Error
invalid-budget-period
Error message
invalid-budget-period
What it means
PolicyEngine.setBudget() rejects a BudgetLimit whose periodMs is <= 0 with Error('invalid-budget-period'). A budget window of zero or negative duration can never accrue, so it is treated as a programming error rather than an empty budget.
Source
Thrown at v3/@claude-flow/security/src/policy/engine.ts:95
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));
}
issueApproval(approval: Omit<PolicyApproval, 'uses' | 'issuedAt'> & { uses?: number; issuedAt?: number }): PolicyApproval {
if (approval.issuedBy === approval.principal) throw new Error('self-approval-forbidden');
if (this.approvalIssuerVerifier?.(approval.issuedBy) !== true) {
throw new Error('untrusted-approval-issuer');
}
const issuedAt = approval.issuedAt ?? this.now();
const record: PolicyApproval = { ...approval, issuedAt, uses: approval.uses ?? 0 };
if (this.state.approvals.some((item) => item.id === record.id)) throw new Error('duplicate-approval-id');
if (!record.idView on GitHub (pinned to fa13ee4ad6)
Solutions
- Set a positive period in milliseconds, e.g. 3_600_000 for one hour.
- When deriving periodMs from timestamps, validate start < end before constructing the limit.
- Reject budget config at load time if periodMs is missing or non-positive.
Example fix
// before
engine.setBudget({ id: 'daily', periodMs: 0, maxCostUsd: 5 });
// after
engine.setBudget({ id: 'daily', periodMs: 24 * 3_600_000, maxCostUsd: 5 }); Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isFinite(budget.periodMs) || budget.periodMs <= 0) {
throw new Error(`budget ${budget.id}: periodMs must be > 0 (got ${budget.periodMs})`);
}
engine.setBudget(budget); Type guard
function hasPositivePeriod(b: BudgetLimit): boolean {
return Number.isFinite(b.periodMs) && b.periodMs > 0;
} Try / catch
try {
engine.setBudget(budget);
} catch (err) {
if (err instanceof Error && err.message === 'invalid-budget-period') {
return configError(`budget '${budget.id}' needs a positive periodMs`);
}
throw err;
} Prevention
- Define budgets in human units (hours/days) and multiply to milliseconds at one call site.
- When computing periodMs from timestamps, assert end > start first.
- Never use 0 as a placeholder for required duration fields.
When it happens
Trigger: setBudget({ id: 'b', periodMs: 0 }) from a default placeholder; periodMs computed as end - start where the timestamps are equal or reversed (negative result); passing seconds (e.g. 60) where a multi-hour window was expected still works, but 0 or negatives throw.
Common situations: Computing the window from two Date.now() calls in quick succession; config files that omit the period key and default it to 0; unit tests constructing budgets with dummy 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
- invalid-budget-limit
- estimatedUsd must be a non-negative finite number
- actualUsd must be a non-negative finite number
- mode must be legacy, observe, or enforce
- Policy canonicalization rejects non-finite numbers
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/a94862e9323958bd.
Report an issue: GitHub.