actualbudget/actual · error · Error
Invalid config file: key "${key}" must be a string, got ${ty
Error message
Invalid config file: key "${key}" must be a string, got ${typeof v} What it means
After confirming a config key is known, validateConfigFileContent checks its value type. String-typed keys (serverUrl, password, sessionToken, syncId, dataDir, encryptionPassword) must hold strings; a number, boolean, or null instead produces this error naming the key and the actual type found.
Source
Thrown at packages/cli/src/config.ts:86
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}`,
);
}
if (
(numberKeys as readonly string[]).includes(key) &&
(typeof v !== 'number' || !Number.isInteger(v) || v < 0)
) {
throw new Error(
`Invalid config file: key "${key}" must be a non-negative integer`,
);
}
if (
(booleanKeys as readonly string[]).includes(key) &&
typeof v !== 'boolean'
) {
throw new Error(
`Invalid config file: key "${key}" must be a boolean, got ${typeof v}`,
);View on GitHub (pinned to d4334cb6e6)
Solutions
- Quote the value so it is a string: "password": "12345"
- Remove the key entirely if the value is unknown (undefined-valued keys are skipped, absent keys are fine)
- For YAML, quote values that look numeric: syncId: "01234567"
Example fix
// before (actual.config.json)
{ "password": 12345, "dataDir": "./data" }
// after
{ "password": "12345", "dataDir": "./data" } Defensive patterns
Strategy: type-guard
Validate before calling
const STRING_KEYS = ['serverUrl','password','sessionToken','syncId','dataDir','encryptionPassword'];
for (const k of STRING_KEYS) {
if (cfg[k] !== undefined && typeof cfg[k] !== 'string') {
throw new Error(`Config key "${k}" must be a string (quote numeric-looking values)`);
}
} Type guard
function stringsAreStrings(v: Record<string, unknown>, keys: readonly string[]): boolean {
return keys.every(k => v[k] === undefined || typeof v[k] === 'string');
} Try / catch
try {
await run(['actual', 'accounts']);
} catch (e) {
if (String(e.message).includes('must be a string')) {
console.error('Quote the named key\'s value in your config file so it parses as a string');
} else throw e;
} Prevention
- Always quote password, token, and syncId values in JSON/YAML — even numeric-looking ones
- In YAML, quote values that could be implicitly typed (e.g. syncId: "01234567")
- If a templating tool writes the config, render values inside quotes
When it happens
Trigger: Writing `"cacheTtl": ...` confusion aside, the typical case is e.g. `"password": 12345` (unquoted number) or `"dataDir": null` explicitly present; YAML configs where a value like `no: yes` coerces oddly or an unquoted port-like string becomes a number; JS config exporting `syncId: undefined` is skipped, but `syncId: 42` throws.
Common situations: Unquoted numeric passwords or API tokens in JSON/YAML; templating tools injecting unquoted values; hand-editing YAML where implicit typing turns "0123" into 123.
Related errors
- Invalid config file: expected an object with keys: ${configF
- Invalid config file: unknown key "${key}"
- 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/02b04f93547cc1d3.
Report an issue: GitHub.