actualbudget/actual · error · Error

getDocumentDir: id is falsy: ${id}

Error message

getDocumentDir: id is falsy: ${id}

What it means

getBudgetDir resolves the on-disk directory `<documentDir>/<budgetId>` and refuses to run when the budget id is falsy (undefined, null, empty string). This guards against silently writing budget files into the document root itself. Note the message text says 'getDocumentDir' for historical reasons but it comes from getBudgetDir.

Source

Thrown at packages/loot-core/src/platform/server/fs/shared.ts:16

// @ts-strict-ignore
import { join } from '#platform/server/fs/path-join';

let documentDir;
export const _setDocumentDir = dir => (documentDir = dir);

export const getDocumentDir = () => {
  if (!documentDir) {
    throw new Error('Document directory is not set');
  }
  return documentDir;
};

export const getBudgetDir = id => {
  if (!id) {
    throw new Error('getDocumentDir: id is falsy: ' + id);
  }

  // TODO: This should be better
  //
  // A cheesy safe guard. The id is generated from the budget name,
  // so it provides an entry point for the user to accidentally (or
  // intentionally) access other parts of the system. Always
  // restrict it to only access files within the budget directory by
  // never allowing slashes.
  if (id.match(/[^A-Za-z0-9\-_]/)) {
    throw new Error(
      `Invalid budget id "${id}". Check the id of your budget in the Advanced section of the settings page.`,
    );
  }

  return join(getDocumentDir(), id);
};

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Validate the budget id is a non-empty string before calling any API that resolves a budget directory.
  2. Trace where the id comes from (budget list, localStorage, query param) and fix the source returning an empty value.
  3. Only call getBudgetDir after a budget has been explicitly loaded/created so a valid id exists.

Example fix

// before
const dir = getBudgetDir(budget?.id);

// after
if (!budget?.id) {
  throw new Error('No budget loaded: cannot resolve budget directory');
}
const dir = getBudgetDir(budget.id);
Defensive patterns

Strategy: validation

Validate before calling

function assertBudgetId(id) {
  if (typeof id !== 'string' || id.length === 0) {
    throw new Error(`Budget id must be a non-empty string, got: ${JSON.stringify(id)}`);
  }
}

Type guard

function isBudgetId(id: unknown): id is string {
  return typeof id === 'string' && id.length > 0;
}

Try / catch

try {
  const dir = getBudgetDir(id);
} catch (e) {
  if (String(e.message).includes('id is falsy')) {
    // no budget loaded — prompt user to open/create one
  } else throw e;
}

Prevention

When it happens

Trigger: Calling getBudgetDir(undefined/null/'') — typically when a budget id failed to load, a caller passed an unset variable, or a remote-id lookup returned nothing before the directory is computed.

Common situations: A budget list returned no id for the requested budget; migration/import code computing a directory before the id is assigned; tests constructing budgets without ids; a stale UI passing a cleared id after budget close.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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