actualbudget/actual · error · Error

Sync ID is required for sync ${flag}. Set --sync-id or ACTUA

Error message

Sync ID is required for sync ${flag}. Set --sync-id or ACTUAL_SYNC_ID.

What it means

The `actual sync <flag>` commands (e.g. --download or --upload) synchronize a specific budget identified by a sync ID. requireSyncIdAndMeta resolves the CLI config and throws when neither the --sync-id flag nor the ACTUAL_SYNC_ID environment variable provides one. No sync target can be determined, so the operation aborts before connecting.

Source

Thrown at packages/cli/src/commands/sync.ts:24

import { CACHE_FILE_NAME, getMetaDir, readCacheState } from '#cache';
import type { CliConfig } from '#config';
import { resolveConfig } from '#config';
import { withConnection } from '#connection';
import { acquireExclusive } from '#lock';
import { printOutput } from '#output';

type SyncCmdOpts = {
  status?: boolean;
  clear?: boolean;
};

async function requireSyncIdAndMeta(
  opts: Record<string, unknown>,
  flag: string,
): Promise<{ config: CliConfig; meta: string }> {
  const config = await resolveConfig(opts);
  if (!config.syncId) {
    throw new Error(
      `Sync ID is required for sync ${flag}. Set --sync-id or ACTUAL_SYNC_ID.`,
    );
  }
  return { config, meta: getMetaDir(config.dataDir, config.syncId) };
}

export function registerSyncCommand(program: Command) {
  program
    .command('sync')
    .description(
      'Sync the local cached budget with the server, print cache status, or clear the cache',
    )
    .option('--status', 'Print cache status without syncing', false)
    .option(
      '--clear',
      'Delete the local cache; next command re-downloads',
      false,
    )

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass the budget's sync ID explicitly: `actual sync --download --sync-id <id>`
  2. Export ACTUAL_SYNC_ID=<id> in your shell profile or CI environment
  3. Find the sync ID from the budget file or server (the ID of the budget you downloaded with `actual download`)
  4. Set syncId in the CLI config file so all commands inherit it

Example fix

// before
actual sync --download

// after
export ACTUAL_SYNC_ID="a1b2c3d4-..."
actual sync --download
# or
actual sync --download --sync-id "a1b2c3d4-..."
Defensive patterns

Strategy: validation

Validate before calling

const syncId = process.env.ACTUAL_SYNC_ID ?? flags.syncId;
if (!syncId) {
  throw new Error('Set ACTUAL_SYNC_ID or pass --sync-id before running `actual sync`');
}

Type guard

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

Try / catch

try {
  await run(['actual', 'sync', '--download']);
} catch (e) {
  if (String(e.message).includes('Sync ID is required')) {
    console.error('Provide --sync-id <id> or export ACTUAL_SYNC_ID=<id>');
  } else throw e;
}

Prevention

When it happens

Trigger: Running `actual sync --download` without --sync-id and with ACTUAL_SYNC_ID unset in the environment; ACTUAL_SYNC_ID set only in an interactive shell profile but the command runs from cron/CI; a config file that omits the syncId key.

Common situations: CI pipelines missing env vars; running sync on a machine where the budget was never downloaded; forgetting that sync IDs are per-budget and changing machines.

Related errors


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