{"record":{"id":"14c3cd3bface782f","repo":"nanocoai/nanoclaw","slug":"flag-must-be-a-json-object-with-string-values","errorCode":null,"errorMessage":"${flag} must be a JSON object with string values","messagePattern":"(.+?) must be a JSON object with string values","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/container-config.ts","lineNumber":203,"sourceCode":"  for (const key of Object.keys(env)) {\n    if (!ENV_KEY_RE.test(key)) {\n      throw new Error(`env key ${JSON.stringify(key)} must be a valid environment variable name`);\n    }\n  }\n  const cwd = parseCwd(input.cwd);\n  return {\n    command,\n    args,\n    env,\n    ...(cwd === undefined ? {} : { cwd }),\n    ...(instructions === undefined ? {} : { instructions }),\n  };\n}\n\nfunction parseStringRecord(value: unknown, flag: string): Record<string, string> | undefined {\n  if (value === undefined) return undefined;\n  if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n    throw new Error(`${flag} must be a JSON object with string values`);\n  }\n  const record: Record<string, string> = {};\n  for (const [key, entry] of Object.entries(value)) {\n    if (typeof entry !== 'string') throw new Error(`${flag} must be a JSON object with string values`);\n    record[key] = entry;\n  }\n  return record;\n}\n\n/** Accept only the spec's fixed cwd shapes, lexically contained (no \"..\" segments). */\nfunction parseCwd(value: unknown): string | undefined {\n  if (value === undefined) return undefined;\n  if (typeof value !== 'string' || !CWD_FORM_RE.test(value)) {\n    throw new Error('cwd must be ./path, ${PLUGIN_ROOT}[/path], or ${PLUGIN_DATA}[/path]');\n  }\n  // rest === '' is the bare form (`${PLUGIN_DATA}`, `./`); empty segments in a\n  // non-empty rest are rejected for symmetry with the command validator.\n  const rest = value.startsWith('./') ? value.slice(2) : value.replace(CWD_FORM_RE, '');","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/container-config.ts#L185-L221","documentation":"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.","triggerScenarios":"headers: \"Authorization: Bearer x\" (string), headers: [\"a\"], env: null passed via the config flag whose name is interpolated into the message (`flag`).","commonSituations":"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.","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)"],"exampleFix":"// before\n{\"headers\":\"X-Api-Key: abc\"}\n// after\n{\"headers\":{\"X-Api-Key\":\"abc\"}}","handlingStrategy":"type-guard","validationCode":"if (v !== undefined && (typeof v !== 'object' || v === null || Array.isArray(v))) throw new UserError('must be a JSON object');","typeGuard":"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'); }","tryCatchPattern":"catch (err) { if (err.message.includes('JSON object with string values')) rePromptAsObject(); else throw err; }","preventionTips":["Always JSON.parse flag values before passing","Use a schema validator at the CLI edge"],"tags":["mcp","config-validation","type-mismatch"],"backgroundTag":"schema-validation-failed","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}