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
- Make the top level a flat object with only the supported keys, e.g. { "serverUrl": "http://localhost:5006", "dataDir": "./data" }
- Flatten nested wrappers — remove any surrounding { "config": ... } or [ ... ] structure
- Check what file cosmiconfig picked up (actual.config.js, .actualclirc, etc.) and inspect its exports
- 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
- Keep the config a flat object; no arrays, wrappers, or profiles
- Run `jq type` / `node -e` validation after regenerating config files
- Keep server options in the sync-server's config, not the CLI's
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
- Invalid config file: unknown key "${key}"
- Invalid config file: key "${key}" must be a string, got ${ty
- Invalid config file: key "${key}" must be a non-negative int
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/d7719be47b283000.
Report an issue: GitHub.