JuliusBrussee/caveman · error · Error

is not a JSON object

Error message

${path} is not a JSON object

What it means

Thrown by parseJsonFileObject when a config file has content but its parsed JSON is not a plain object — e.g. a top-level array, string, number, or a scalar like `true`. MCP config management requires JSON object documents and refuses to operate on (and overwrite) files with an unexpected top-level shape.

Solutions

  1. Inspect the file: `cat <path>` and check its top-level JSON type
  2. Convert the content to a JSON object (e.g. `{}`) or restore it from a backup
  3. If the file is disposable, delete it and let the CLI regenerate it
  4. Re-run the MCP setup command after fixing the file

Example fix

// before (config.json)
[]
// after
{}
Defensive patterns

Strategy: validation

Validate before calling

const bytes = fs.readFileSync(configPath);
const parsed = JSON.parse(bytes.toString("utf8"));
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
  console.error(`${configPath} must contain a JSON object at the top level`);
}

Type guard

function isJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  runMcpCommand();
} catch (e) {
  if (String(e.message).includes("is not a JSON object")) {
    console.error(`Reset the named file to a JSON object (e.g. {}) or restore a backup.`);
  }
}

Prevention

When it happens

Trigger: parseJsonFileObject reads non-empty bytes whose JSON.parse result is null, a non-object, or an Array; e.g. a config file containing `[]` or `"text"`.

Common situations: A config file overwritten by another tool with an array or scalar, an empty placeholder replaced with quoted text, or a truncated/invalid write that still parses as non-object JSON.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/c6220a998722ae1a. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:6980

    if (fd !== undefined) try { closeSync(fd); } catch { /* original error wins */ }
    try { unlinkSync(temp); } catch { /* no partial */ }
    throw error;
  }
}

function durableUnlink(path: string): void {
  try {
    unlinkSync(path);
    fsyncParentDirectory(path);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

function parseJsonFileObject(path: string, bytes: Buffer | null): Record<string, unknown> {
  if (!bytes || bytes.length === 0) return {};
  const parsed = JSON.parse(bytes.toString("utf8"));
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${path} is not a JSON object`);
  return parsed as Record<string, unknown>;
}

function nativeMcpBinaryRequired(): string {
  const compatible = probeMcpBinary();
  if (!compatible) throw new Error("caveman-mcp not found; run `caveman setup --install`");
  if (!compatible.probe.current) throw new Error(`caveman-mcp ${compatible.probe.version} lacks current mcp_recovery capability; run \`caveman setup --install\``);
  return compatible.binary;
}

function nativeProxyBinaryRequired(gw: string): void {
  if (wrapMode(gw) !== "local") return;
  const binary = resolveGoBin("caveman-proxy", "CAVEMAN_PROXY_BIN");
  if (!binary) throw new Error("caveman-proxy not found; run `caveman setup --install`");
  const probe = probeVersionedBinary(binary, "native_runtime_v1");
  if (!probe.current) throw new Error(`caveman-proxy ${probe.version} lacks current native_runtime_v1 capability; run \`caveman setup --install\``);
}

View on GitHub (pinned to 3ee70a1026)