actualbudget/actual · error · CompileError

Invalid path: ${path}

Error message

Invalid path: ${path}

What it means

makePath (compiler.ts:71) resolves a dotted relationship path used by AQL field references. A valid path must contain at least a table and a field (e.g. 'payee.name'). If the path has fewer than two dot-separated parts, the compiler cannot form a join and throws this CompileError.

Source

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

  if (schema[tableName] == null) {
    throw new CompileError(`Table "${tableName}" does not exist in the schema`);
  }

  const fieldDesc = schema[tableName][field];
  if (fieldDesc == null) {
    throw new CompileError(
      `Field "${field}" does not exist in table "${tableName}"`,
    );
  }
  return fieldDesc;
}

function makePath(state, path) {
  const { schema, paths } = state;

  const parts = path.split('.');
  if (parts.length < 2) {
    throw new CompileError('Invalid path: ' + path);
  }

  const initialTable = parts[0];

  const tableName = parts.slice(1).reduce((tableName, field) => {
    const table = schema[tableName];

    if (table == null) {
      throw new CompileError(`Path error: ${tableName} table does not exist`);
    }

    if (!table[field] || table[field].ref == null) {
      throw new CompileError(
        `Field not joinable on table ${tableName}: "${field}"`,
      );
    }

    return table[field].ref;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Append the field name to the path: use 'payee.name' instead of 'payee'.
  2. If you want the raw id column of the implicit table, use an unqualified field like 'id' (no path) rather than a one-part path.
  3. Inspect the dynamic code that builds the field string and ensure it always produces at least 'table.field'.

Example fix

// before
q('transactions').select('payee')
// after
q('transactions').select('payee.name')
Defensive patterns

Strategy: validation

Validate before calling

function assertPath(path) {
  if (typeof path !== 'string' || path.split('.').length < 2) {
    throw new Error(`Field path must be "table.field": got ${path}`);
  }
}
assertPath('payee.name');

Type guard

function isFieldPath(v) {
  return typeof v === 'string' && /^[A-Za-z_][\w]*\.[A-Za-z_][\w]*$/.test(v);
}

Try / catch

try {
  runQuery(q);
} catch (e) {
  if (String(e.message).startsWith('Invalid path:')) {
    console.warn('Dotted field path required, e.g. payee.name');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling resolvePath/transformField with a bare table name such as q('transactions').select('payee') or filter({ 'account': ... }) — a single segment with no '.field' part.

Common situations: Passing a relationship/table name instead of 'relation.field'; dynamically building field strings and ending up with just the prefix; copy-pasting a path and dropping the '.field' suffix; rules or reports configured with an incomplete field path.

Related errors


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