actualbudget/actual · error · Error

Sync ID is required for this command. Set --sync-id or ACTUA

Error message

Sync ID is required for this command. Set --sync-id or ACTUAL_SYNC_ID.

What it means

withConnection requires a sync id for any command that operates on a budget (i.e. when skipBudget is false). After the auth check, if config.syncId is falsy it throws this error, because the command cannot know which budget file to lock, open, and sync.

Source

Thrown at packages/cli/src/connection.ts:72

      verbose: globalOpts.verbose,
    });
  } else if (config.password) {
    await api.init({
      serverURL: config.serverUrl,
      dataDir: config.dataDir,
      password: config.password,
      verbose: globalOpts.verbose,
    });
  } else {
    throw new Error(
      'Authentication required. Provide --password or --session-token, or set ACTUAL_PASSWORD / ACTUAL_SESSION_TOKEN.',
    );
  }

  try {
    if (skipBudget) return await fn(config);
    if (!config.syncId) {
      throw new Error(
        'Sync ID is required for this command. Set --sync-id or ACTUAL_SYNC_ID.',
      );
    }

    const meta = getMetaDir(config.dataDir, config.syncId);
    let release: Release | null = null;
    if (!config.noLock) {
      release = mutates
        ? await acquireExclusive(meta, {
            timeoutMs: config.lockTimeout * 1000,
          })
        : await acquireShared(meta, {
            timeoutMs: config.lockTimeout * 1000,
          });
    }

    try {
      const cachedState = readCacheState(meta);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass --sync-id <id> to the command (the budget's groupId or cloudFileId).
  2. Export ACTUAL_SYNC_ID in the environment or CI secrets.
  3. Add syncId to the config file if you always work with the same budget.
  4. Run the budgets command first to discover available sync ids.

Example fix

// before
actual-cli transactions --data '{...}'
// after
actual-cli transactions --sync-id "$ACTUAL_SYNC_ID" --data '{...}'
Defensive patterns

Strategy: validation

Validate before calling

const syncId = cliOpts.syncId ?? process.env.ACTUAL_SYNC_ID;
if (!syncId) {
  throw new Error('A sync id is required: pass --sync-id or export ACTUAL_SYNC_ID.');
}

Type guard

function hasSyncId(c: { syncId?: string }): c is typeof c & { syncId: string } {
  return typeof c.syncId === 'string' && c.syncId.length > 0;
}

Try / catch

try {
  await withConnection(opts, fn);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Sync ID is required')) {
    console.error('Pass --sync-id (budget groupId/cloudFileId) or set ACTUAL_SYNC_ID.');
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Running accounts/budgets/categories/payees/query commands without --sync-id and without ACTUAL_SYNC_ID set; only commands that skip budget loading (skipBudget) may omit it.

Common situations: New users assuming the CLI auto-detects the single local budget; scripts migrated from an API-only workflow where the budget id was passed differently; env var not exported inside a subshell/CI step.

Related errors


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