{"record":{"id":"15984c828a064c50","repo":"JuliusBrussee/caveman","slug":"merged-config-is-not-json","errorCode":null,"errorMessage":"merged config is not JSON","messagePattern":"merged config is not JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8883,"sourceCode":"      rawOverlay = deepMerge(rawOverlay, {\n        mcp: { excluded: [...new Set([...preserved, \"caveman\"])] },\n      });\n    }\n  }\n  const overlay = (mcpMode === \"auto\" ? rawOverlay : withoutCavemanMcpServer(rawOverlay as JsonObject)) as JsonObject;\n  const merged = deepMerge(baseConfig, overlay) as JsonObject;\n  if (agent.id === \"qwen\") {\n    // Qwen treats these as whole-object REPLACE settings. Our generic merge is\n    // intentionally conservative for other agents, but retaining a sibling\n    // provider here would make it selectable at runtime outside /w/qwen.\n    for (const key of [\"modelProviders\", \"providerProtocol\"] as const) {\n      const value = overlay[key];\n      if (value === undefined) throw new Error(`Qwen routed profile is missing ${key}`);\n      merged[key] = cloneJsonValue(value);\n    }\n  }\n  const rendered = JSON.stringify(merged, null, 2);\n  if (rendered === undefined) throw new Error(\"merged config is not JSON\");\n  const outDir = mkdtempSync(join(tmpdir(), \"caveman-wrap-\"));\n  const outPath = join(outDir, `${agent.id}.json`);\n  writeFileSync(outPath, rendered + \"\\n\", { mode: 0o600 });\n  wrapTempDirs.add(outDir);\n  env[inj.env_var] = outPath;\n}\n\nfunction cleanupWrapTempDirs() {\n  for (const dir of [...wrapTempDirs]) {\n    try {\n      rmSync(dir, { recursive: true, force: true });\n    } catch {\n      // Temp cleanup is best-effort; wrap must never fail after child exit.\n    } finally {\n      wrapTempDirs.delete(dir);\n    }\n  }\n}","sourceCodeStart":8865,"sourceCodeEnd":8901,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L8865-L8901","documentation":"applyConfigFileInjection merges the agent's base config with Caveman's overlay (deepMerge) and writes the result to a temp file that the wrapped agent is pointed at via env var. JSON.stringify returns undefined only when the value being serialized is itself undefined, so this error means the merged config root came out undefined — an internal invariant break (base configs come from JSON.parse and overlays from built-in builders), not a user-parseable input problem. It aborts wrap before the child process starts, so no half-configured agent is launched.","triggerScenarios":"`caveman wrap <agent>` (or the agent shortcut) for an agent with a config-file injection (e.g. gemini/opencode profiles) where a dynamic overlay builder returned undefined or deepMerge produced an undefined root — effectively only reachable via a Caveman bug or a corrupted bundled profile definition.","commonSituations":"Hitting a genuine bug in a specific Caveman release (e.g. a builder refactor returning undefined for one agent); running a modified/local build of the CLI; an agent profile whose base config file exists but reads as empty/absent in an unexpected way.","solutions":["Update Caveman to the latest release — this is an internal invariant, likely already fixed","Run `caveman wrap <agent> --no-config` style bypass if available, or wrap a different agent to confirm the CLI build is broken vs the specific profile","Check that the agent's base config file (the path the profile injects, e.g. gemini settings) exists and is valid JSON; rename it aside temporarily and retry","Report it: include the agent id, caveman version (`caveman --version`), and whether a custom overlay/config is in play"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Sanity-check the agent's base config parses before invoking wrap\nimport { readFileSync } from 'node:fs';\ntry {\n  JSON.parse(readFileSync(baseConfigPathForAgent, 'utf8'));\n} catch (err) {\n  console.error('base config is not valid JSON — fix it before caveman wrap');\n}","typeGuard":"const isJsonObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"catch (err) { if (err.message === 'merged config is not JSON') { /* internal invariant: capture `caveman --version` + agent id and report upstream; do not retry blindly */ } else throw err; }","preventionTips":["Keep agent base config files valid JSON (they are also read by the agents themselves)","Pin a known-good caveman version in CI instead of floating latest if you hit a broken release","Update caveman promptly — invariant bugs are release-fixed"],"tags":["json","serialization","config-merge","wrap","invariant"],"backgroundTag":"json-serialization-failed","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}