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

  1. Update Caveman to the latest release — this is an internal invariant, likely already fixed
  2. 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
  3. 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
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06). Data as JSON: /api/errors/15984c828a064c50. Report an issue: GitHub.