{"record":{"id":"6b54a74cd6f06c45","repo":"actualbudget/actual","slug":"sync-id-is-required-for-this-command-set-sync-i","errorCode":null,"errorMessage":"Sync ID is required for this command. Set --sync-id or ACTUAL_SYNC_ID.","messagePattern":"Sync ID is required for this command\\. Set --sync-id or ACTUAL_SYNC_ID\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/connection.ts","lineNumber":72,"sourceCode":"      verbose: globalOpts.verbose,\n    });\n  } else if (config.password) {\n    await api.init({\n      serverURL: config.serverUrl,\n      dataDir: config.dataDir,\n      password: config.password,\n      verbose: globalOpts.verbose,\n    });\n  } else {\n    throw new Error(\n      'Authentication required. Provide --password or --session-token, or set ACTUAL_PASSWORD / ACTUAL_SESSION_TOKEN.',\n    );\n  }\n\n  try {\n    if (skipBudget) return await fn(config);\n    if (!config.syncId) {\n      throw new Error(\n        'Sync ID is required for this command. Set --sync-id or ACTUAL_SYNC_ID.',\n      );\n    }\n\n    const meta = getMetaDir(config.dataDir, config.syncId);\n    let release: Release | null = null;\n    if (!config.noLock) {\n      release = mutates\n        ? await acquireExclusive(meta, {\n            timeoutMs: config.lockTimeout * 1000,\n          })\n        : await acquireShared(meta, {\n            timeoutMs: config.lockTimeout * 1000,\n          });\n    }\n\n    try {\n      const cachedState = readCacheState(meta);","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/cli/src/connection.ts#L54-L90","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass --sync-id <id> to the command (the budget's groupId or cloudFileId).","Export ACTUAL_SYNC_ID in the environment or CI secrets.","Add syncId to the config file if you always work with the same budget.","Run the budgets command first to discover available sync ids."],"exampleFix":"// before\nactual-cli transactions --data '{...}'\n// after\nactual-cli transactions --sync-id \"$ACTUAL_SYNC_ID\" --data '{...}'","handlingStrategy":"validation","validationCode":"const syncId = cliOpts.syncId ?? process.env.ACTUAL_SYNC_ID;\nif (!syncId) {\n  throw new Error('A sync id is required: pass --sync-id or export ACTUAL_SYNC_ID.');\n}","typeGuard":"function hasSyncId(c: { syncId?: string }): c is typeof c & { syncId: string } {\n  return typeof c.syncId === 'string' && c.syncId.length > 0;\n}","tryCatchPattern":"try {\n  await withConnection(opts, fn);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Sync ID is required')) {\n    console.error('Pass --sync-id (budget groupId/cloudFileId) or set ACTUAL_SYNC_ID.');\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Set ACTUAL_SYNC_ID in shell profiles/CI for the default budget.","Discover ids with the budgets listing command and persist them in your config file.","Guard wrapper scripts with `[ -n \"$ACTUAL_SYNC_ID\" ] || exit 1`.","Remember only commands that operate on a budget need it."],"tags":["cli","config","missing-option","budget"],"backgroundTag":"missing-required-option","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}