JuliusBrussee/caveman · error · Error
merged config is not JSON
Error message
merged config is not JSON
What it means
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.
Source
Thrown at packages/cli/src/index.ts:8883
rawOverlay = deepMerge(rawOverlay, {
mcp: { excluded: [...new Set([...preserved, "caveman"])] },
});
}
}
const overlay = (mcpMode === "auto" ? rawOverlay : withoutCavemanMcpServer(rawOverlay as JsonObject)) as JsonObject;
const merged = deepMerge(baseConfig, overlay) as JsonObject;
if (agent.id === "qwen") {
// Qwen treats these as whole-object REPLACE settings. Our generic merge is
// intentionally conservative for other agents, but retaining a sibling
// provider here would make it selectable at runtime outside /w/qwen.
for (const key of ["modelProviders", "providerProtocol"] as const) {
const value = overlay[key];
if (value === undefined) throw new Error(`Qwen routed profile is missing ${key}`);
merged[key] = cloneJsonValue(value);
}
}
const rendered = JSON.stringify(merged, null, 2);
if (rendered === undefined) throw new Error("merged config is not JSON");
const outDir = mkdtempSync(join(tmpdir(), "caveman-wrap-"));
const outPath = join(outDir, `${agent.id}.json`);
writeFileSync(outPath, rendered + "\n", { mode: 0o600 });
wrapTempDirs.add(outDir);
env[inj.env_var] = outPath;
}
function cleanupWrapTempDirs() {
for (const dir of [...wrapTempDirs]) {
try {
rmSync(dir, { recursive: true, force: true });
} catch {
// Temp cleanup is best-effort; wrap must never fail after child exit.
} finally {
wrapTempDirs.delete(dir);
}
}
}View on GitHub (pinned to 5184b3d11a)
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
Defensive patterns
Strategy: try-catch
Validate before calling
// Sanity-check the agent's base config parses before invoking wrap
import { readFileSync } from 'node:fs';
try {
JSON.parse(readFileSync(baseConfigPathForAgent, 'utf8'));
} catch (err) {
console.error('base config is not valid JSON — fix it before caveman wrap');
} Type guard
const isJsonObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
Try / catch
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; } Prevention
- 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
When it happens
Trigger: `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.
Common situations: 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.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- caveman agent: value is not canonically serializable
- assembly slot ${JSON.stringify(slot.id)} content is not JSON
- assembly slot {slot.id!r} content is not JSON-serializable
- cave_harness_wire_contract_invalid
- cave_live_eval_sandbox_profile_invalid
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/15984c828a064c50.
Report an issue: GitHub.