actualbudget/actual · error · CompileError

This function cannot track error data. It needs to accept th

Error message

This function cannot track error data. It needs to accept the compiler state as the first argument.

What it means

saveStack wraps compiler functions so errors can report the failing expression via state.compileStack. If a wrapped function is invoked without a compiler state (or one lacking compileStack) as its first argument, the compiler throws this CompileError — it's an internal contract violation, usually caused by calling a compiled function directly instead of through the normal compilation path.

Source

Thrown at packages/loot-core/src/server/aql/compiler.ts:386

function validateArgLength(arr: unknown[], min: number, max?: number) {
  if (max == null) {
    max = min;
  }

  if (min != null && arr.length < min) {
    throw new CompileError('Too few arguments');
  }
  if (max != null && arr.length > max) {
    throw new CompileError('Too many arguments');
  }
}

//// Nice errors

function saveStack(type, func) {
  return (state, ...args) => {
    if (state == null || state.compileStack == null) {
      throw new CompileError(
        'This function cannot track error data. ' +
          'It needs to accept the compiler state as the first argument.',
      );
    }

    state.compileStack.push({ type, args });
    const ret = func(state, ...args);
    state.compileStack.pop();
    return ret;
  };
}

function prettyValue(value) {
  if (typeof value === 'string') {
    return value;
  } else if (value === undefined) {
    return 'undefined';
  }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Always pass the compiler state as the first argument when invoking these functions
  2. Route query compilation through the public compileQuery/aql API instead of internal functions
  3. If adding a custom op/function, register it so the normal saveStack-wrapped path runs with a valid state
  4. Initialize a proper state object (including compileStack) in test harnesses that call compiler internals

Example fix

// before
compileExpr(null, expr)
// after
const state = makeCompilerState(schema); // includes compileStack: []
compileExpr(state, expr)
Defensive patterns

Strategy: try-catch

Validate before calling

function assertCompilerState(state) {
  if (state == null || state.compileStack == null) {
    throw new Error('Compiler functions require a state with compileStack as first arg');
  }
}

Type guard

function hasCompilerState(args) {
  return args.length > 0 && args[0] != null && Array.isArray(args[0].compileStack);
}

Try / catch

try {
  return compileExpr(state, expr);
} catch (e) {
  if (/cannot track error data/.test(e.message)) {
    throw new Error('Internal misuse: pass the compiler state as first argument or use the public aql API', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a wrapped compiler function (compileExpr, compileFunction, compileOp, compileWhere, compileSelect, compileGroupBy) without a state object first argument, or with state=null; custom code reusing internal compiler functions with a wrong signature.

Common situations: Monkey-patching or extending the compiler with custom functions that don't thread `state`; calling internal APIs in tests without constructing a state; refactors that changed the argument order.

Related errors


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