actualbudget/actual · error · CompileError

Path does not exist:

Error message

Path does not exist: 

What it means

For deeply nested paths (3+ segments), makePath (compiler.ts:104) requires that the parent path was already resolved and registered in state.paths. If the parent path (all segments except the last) is missing from the paths map, this CompileError is thrown — joins must be established outer-to-inner.

Source

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

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

    return table[field].ref;
  }, initialTable);

  let joinTable;
  const parentParts = parts.slice(0, -1);
  if (parentParts.length === 1) {
    joinTable = parentParts[0];
  } else {
    const parentPath = parentParts.join('.');
    const parentDesc = paths.get(parentPath);
    if (!parentDesc) {
      throw new CompileError('Path does not exist: ' + parentPath);
    }
    joinTable = parentDesc.tableId;
  }

  return {
    tableName,
    tableId: uid(tableName),
    joinField: parts[parts.length - 1],
    joinTable,
  };
}

function resolvePath(state, path) {
  let paths = path.split('.');

  paths = paths.reduce(
    (acc, name) => {
      const fullName = acc.context + '.' + name;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Resolve the full path through normal query APIs (q.select/q.filter with the dotted string) so parents are registered automatically via resolvePath.
  2. If calling internals, register parent paths in state.paths before the child path.
  3. Simplify overly deep paths — Actual's schema rarely needs more than two segments; verify the relationship exists.

Example fix

// before (manual, out of order)
makePath(state, 'transactions.category.group.id');
// after
resolvePath(state, 'transactions.category');
makePath(state, 'transactions.category.group');
Defensive patterns

Strategy: validation

Validate before calling

function assertParentsResolved(state, path) {
  const parts = path.split('.');
  for (let i = 1; i < parts.length; i++) {
    const parent = parts.slice(0, i).join('.');
    if (i > 1 && !state.paths.get(parent)) {
      throw new Error(`Parent path not resolved: ${parent}`);
    }
  }
}

Type guard

null

Try / catch

try {
  makePath(state, deepPath);
} catch (e) {
  if (e.message.startsWith('Path does not exist:')) {
    // resolve parent paths first via resolvePath, then retry once
    resolvePath(state, deepPath.split('.').slice(0, -1).join('.'));
  } else throw e;
}

Prevention

When it happens

Trigger: Requesting a nested path whose parent was never resolved first, e.g. building path info directly via makePath for 'a.b.c' when 'a.b' was never put through resolvePath/makePath; in practice triggered by manually constructing nested field references out of order.

Common situations: Custom tooling calling internal compiler functions directly; constructing query state programmatically and skipping intermediate path registration; unusual nested relationships referenced before their parents in the same expression.

Related errors


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