{"record":{"id":"c6220a998722ae1a","repo":"JuliusBrussee/caveman","slug":"path-is-not-a-json-object","errorCode":null,"errorMessage":"${path} is not a JSON object","messagePattern":"(.+?) is not a JSON object","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5999,"sourceCode":"}\n\nfunction atomicWriteFile(path: string, bytes: Buffer, mode = 0o600): void {\n  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n  const temp = join(dirname(path), `.${basename(path)}.caveman-${process.pid}-${randomUUID()}.tmp`);\n  try {\n    writeFileSync(temp, bytes, { mode });\n    renameSync(temp, path);\n    chmodSync(path, mode);\n  } catch (error) {\n    try { unlinkSync(temp); } catch { /* no partial */ }\n    throw error;\n  }\n}\n\nfunction parseJsonFileObject(path: string, bytes: Buffer | null): Record<string, unknown> {\n  if (!bytes || bytes.length === 0) return {};\n  const parsed = JSON.parse(bytes.toString(\"utf8\"));\n  if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(`${path} is not a JSON object`);\n  return parsed as Record<string, unknown>;\n}\n\nfunction nativeMcpBinaryRequired(): string {\n  const compatible = probeMcpBinary();\n  if (!compatible) throw new Error(\"caveman-mcp not found; run `caveman setup --install`\");\n  if (!compatible.probe.current) throw new Error(`caveman-mcp ${compatible.probe.version} lacks current mcp_recovery capability; run \\`caveman setup --install\\``);\n  return compatible.binary;\n}\n\nfunction nativeProxyBinaryRequired(gw: string): void {\n  if (wrapMode(gw) !== \"local\") return;\n  const binary = resolveGoBin(\"caveman-proxy\", \"CAVEMAN_PROXY_BIN\");\n  if (!binary) throw new Error(\"caveman-proxy not found; run `caveman setup --install`\");\n  const probe = probeVersionedBinary(binary, \"native_runtime_v1\");\n  if (!probe.current) throw new Error(`caveman-proxy ${probe.version} lacks current native_runtime_v1 capability; run \\`caveman setup --install\\``);\n}\n","sourceCodeStart":5981,"sourceCodeEnd":6017,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L5981-L6017","documentation":"parseJsonFileObject parses agent/config file bytes: empty or missing files yield {}, but if the parsed top-level value is not a JSON object (array, string, number, boolean, null) it throws with the file path. Note that syntactically invalid JSON surfaces as a JSON.parse SyntaxError before this check, not this message.","triggerScenarios":"Any config read path using parseJsonFileObject (agent settings, MCP config) where the file's top level is a JSON array like [\"a\",\"b\"] or a bare scalar/string, or the literal \"null\".","commonSituations":"User hand-wrote a JSON array at top level; a different tool serialized a list into a config path; truncated writes producing valid scalar JSON is rare but arrays are the common case.","solutions":["Open the named file and wrap its contents in an object: { \"items\": <existing array> } or restructure per the expected schema","If the file is disposable config, delete it (empty/missing is tolerated as {}) and let caveman regenerate","Validate with `jq -e 'type == \"object\"' <file>` before running setup"],"exampleFix":"// before (mcp.json)\n[ { \"name\": \"caveman-cloud\" } ]\n\n// after\n{ \"mcpServers\": { \"caveman-cloud\": { } } }","handlingStrategy":"type-guard","validationCode":"import { readFileSync } from \"node:fs\";\nlet parsed: unknown;\ntry { parsed = JSON.parse(readFileSync(path, \"utf8\")); }\ncatch { throw new Error(`${path} is not valid JSON`); }\nif (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n  // safe: plain object\n} else {\n  throw new Error(`${path} top level is ${Array.isArray(parsed) ? \"array\" : typeof parsed}; expected object`);\n}","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const root = parseJsonFileObject(path, bytes);\n} catch (error) {\n  if (/is not a JSON object/.test((error as Error).message)) {\n    // schema issue in user-owned file: surface path and expected shape; never auto-overwrite\n    reportConfigSchemaError(path);\n  } else if (error instanceof SyntaxError) {\n    reportConfigParseError(path);   // malformed JSON is a different failure\n  } else throw error;\n}","preventionTips":["Top-level of agent config files must always be a JSON object","Distinguish SyntaxError (invalid JSON) from this object-shape error when reporting","Use `jq -e 'type == \"object\"' <file>` in CI checks on config files"],"tags":["json","config","schema","parse"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}