nanocoai/nanoclaw · error · Error
${flag} must be a JSON object with string values
Error message
${flag} must be a JSON object with string values What it means
parseStringRecord (used for MCP `headers` and `env`) rejects a value that is present but not a plain object — arrays, null, strings, or numbers all fail. The first check (container-config.ts:203) fires before per-entry inspection.
Source
Thrown at src/container-config.ts:203
for (const key of Object.keys(env)) {
if (!ENV_KEY_RE.test(key)) {
throw new Error(`env key ${JSON.stringify(key)} must be a valid environment variable name`);
}
}
const cwd = parseCwd(input.cwd);
return {
command,
args,
env,
...(cwd === undefined ? {} : { cwd }),
...(instructions === undefined ? {} : { instructions }),
};
}
function parseStringRecord(value: unknown, flag: string): Record<string, string> | undefined {
if (value === undefined) return undefined;
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`${flag} must be a JSON object with string values`);
}
const record: Record<string, string> = {};
for (const [key, entry] of Object.entries(value)) {
if (typeof entry !== 'string') throw new Error(`${flag} must be a JSON object with string values`);
record[key] = entry;
}
return record;
}
/** Accept only the spec's fixed cwd shapes, lexically contained (no ".." segments). */
function parseCwd(value: unknown): string | undefined {
if (value === undefined) return undefined;
if (typeof value !== 'string' || !CWD_FORM_RE.test(value)) {
throw new Error('cwd must be ./path, ${PLUGIN_ROOT}[/path], or ${PLUGIN_DATA}[/path]');
}
// rest === '' is the bare form (`${PLUGIN_DATA}`, `./`); empty segments in a
// non-empty rest are rejected for symmetry with the command validator.
const rest = value.startsWith('./') ? value.slice(2) : value.replace(CWD_FORM_RE, '');View on GitHub (pinned to 294ef2aee8)
Solutions
- Provide a JSON object: {"headers":{"Authorization":"Bearer x"}}
- Check shell quoting so the flag value parses as JSON, not a string
- Remove the field if unused (undefined is accepted)
Example fix
// before
{"headers":"X-Api-Key: abc"}
// after
{"headers":{"X-Api-Key":"abc"}} Defensive patterns
Strategy: type-guard
Validate before calling
if (v !== undefined && (typeof v !== 'object' || v === null || Array.isArray(v))) throw new UserError('must be a JSON object'); Type guard
function isStringRecord(v: unknown): v is Record<string, string> { return typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every(x => typeof x === 'string'); } Try / catch
catch (err) { if (err.message.includes('JSON object with string values')) rePromptAsObject(); else throw err; } Prevention
- Always JSON.parse flag values before passing
- Use a schema validator at the CLI edge
When it happens
Trigger: headers: "Authorization: Bearer x" (string), headers: ["a"], env: null passed via the config flag whose name is interpolated into the message (`flag`).
Common situations: Passing YAML-ish or HTTP-text header blocks instead of JSON objects; shell quoting that turns the object into a string; null from optional-field templating.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- MCP instructions must be a string
- args must be a JSON array of strings
- type "stdio" requires command
- type "http" requires url
- Provide exactly one of command or url
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/14c3cd3bface782f.
Report an issue: GitHub.