actualbudget/actual · error · RuleError

${msg}

Error message

${msg}

What it means

The `assert` helper in rule-utils.ts throws a `RuleError` whenever a falsy test value is passed, with a caller-supplied `type` and message. It is the standard invariant-check used throughout the rules engine (rule construction, condition parsing, test execution). Any rule definition or serialized rule data that violates the rules API contract will surface here.

Source

Thrown at packages/loot-core/src/server/rules/rule-utils.ts:14

// @ts-strict-ignore
import * as dateFns from 'date-fns';

import { logger } from '#platform/server/log';
import { RuleError } from '#server/errors';
import { RSchedule } from '#server/util/rschedule';
import { recurConfigToRSchedule } from '#shared/schedules';
import type { RuleConditionEntity } from '#types/models';

import type { Rule } from './rule';

export function assert(test: unknown, type: string, msg: string): asserts test {
  if (!test) {
    throw new RuleError(type, msg);
  }
}

const OP_SCORES: Record<RuleConditionEntity['op'], number> = {
  is: 10,
  isNot: 10,
  oneOf: 9,
  notOneOf: 9,
  isapprox: 5,
  isbetween: 5,
  gt: 1,
  gte: 1,
  lt: 1,
  lte: 1,
  contains: 0,
  doesNotContain: 0,
  matches: 0,
  hasTags: 0,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Log the full rule/condition object being processed to identify which invariant failed
  2. Validate rule conditions (op, field, value all present and valid) before calling runRules/new Rule
  3. Migrate or fix malformed rule rows in the budget database
  4. Upgrade to the latest version in case the rule schema changed between versions

Example fix

// before
new Rule({ conditions: [{ op: 'is' }] }); // missing field/value -> assert throws
// after
new Rule({
  conditions: [{ field: 'payee', op: 'is', value: 'amazon' }],
  actions: [{ op: 'set', field: 'category', value: 'Shopping' }],
});
Defensive patterns

Strategy: try-catch

Validate before calling

function isValidRuleInput(rule) {
  return (
    Array.isArray(rule.conditions) && rule.conditions.length > 0 &&
    rule.conditions.every(c => c.op && c.field) &&
    Array.isArray(rule.actions) && rule.actions.every(a => a.op && a.field)
  );
}
if (!isValidRuleInput(input)) throw new Error('Invalid rule input');

Type guard

function isRuleCondition(c) {
  return typeof c === 'object' && c !== null &&
    typeof c.field === 'string' && typeof c.op === 'string' &&
    'value' in c;
}

Try / catch

import { RuleError } from './rule-error';
try {
  const rule = new Rule(rawRule);
  rule.runTest(transaction);
} catch (e) {
  if (e instanceof RuleError) {
    console.error(`Rule error [${e.type}]: ${e.message}`);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Any call to `assert(value, type, msg)` where `value` is falsy — e.g. `runTest` on a rule with a malformed condition, `new Rule(...)` with missing fields, or `parse` of a rule with an invalid operator/field combination.

Common situations: Programmatically building Rule objects via the API with missing `op`, `field`, or `value`; deserializing rules from an older/edited budget database where condition shapes changed; typos in custom rule conditions.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/cfa0b9db707ff582. Report an issue: GitHub.