actualbudget/actual · error · Error

Invalid config file: expected an object with keys: ${configF

Error message

Invalid config file: expected an object with keys: ${configFileKeys.join(', ')}

What it means

The Actual CLI loads its configuration from a config file via cosmiconfig and validates it with validateConfigFileContent. The file's top level must be a JSON/YAML object whose keys are all from the known set (serverUrl, password, sessionToken, syncId, dataDir, encryptionPassword, cacheTtl, lockTimeout, noLock). A top-level array, string, or null is rejected with this error listing the allowed keys.

Source

Thrown at packages/cli/src/config.ts:71

  password?: string;
  sessionToken?: string;
  syncId?: string;
  dataDir?: string;
  encryptionPassword?: string;
  cacheTtl?: number;
  lockTimeout?: number;
  noLock?: boolean;
};

const configFileKeys: readonly string[] = [
  ...stringKeys,
  ...numberKeys,
  ...booleanKeys,
];

function validateConfigFileContent(value: unknown): ConfigFileContent {
  if (!isRecord(value)) {
    throw new Error(
      'Invalid config file: expected an object with keys: ' +
        configFileKeys.join(', '),
    );
  }
  for (const key of Object.keys(value)) {
    if (!configFileKeys.includes(key)) {
      throw new Error(`Invalid config file: unknown key "${key}"`);
    }
    const v = value[key];
    if (v === undefined) continue;
    if (
      (stringKeys as readonly string[]).includes(key) &&
      typeof v !== 'string'
    ) {
      throw new Error(
        `Invalid config file: key "${key}" must be a string, got ${typeof v}`,
      );
    }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Make the top level a flat object with only the supported keys, e.g. { "serverUrl": "http://localhost:5006", "dataDir": "./data" }
  2. Flatten nested wrappers — remove any surrounding { "config": ... } or [ ... ] structure
  3. Check what file cosmiconfig picked up (actual.config.js, .actualclirc, etc.) and inspect its exports
  4. If the file is a JS config, ensure it module.exports an object, not a function or array

Example fix

// before (actual.config.js)
module.exports = [
  { serverUrl: 'http://localhost:5006' }
];

// after
module.exports = {
  serverUrl: 'http://localhost:5006',
  dataDir: './data'
};
Defensive patterns

Strategy: type-guard

Validate before calling

const cfg = JSON.parse(readFileSync(configPath, 'utf8'));
if (cfg === null || typeof cfg !== 'object' || Array.isArray(cfg)) {
  throw new Error(`${configPath} must be a flat object of CLI options`);
}

Type guard

function isConfigObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Try / catch

try {
  await run(['actual', 'query', 'transactions']);
} catch (e) {
  if (String(e.message).includes('Invalid config file')) {
    console.error(`Fix ${configPath}: top level must be an object with only CLI option keys`);
  } else throw e;
}

Prevention

When it happens

Trigger: A .actual-cli-config.json / actual.config.json containing an array or a bare string; a file that is empty or contains only a comment; YAML files where the top level is a list; a file holding credentials in a nested structure like { "config": {...} }.

Common situations: Copying an example into the wrong file; tools generating config as an array of profiles; accidental overwrite of the config with output of another command.

Related errors


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