JuliusBrussee/caveman · error · CaveRequestError
cave response must be a JSON object
Error message
cave response must be a JSON object
What it means
After the shared caveFetch helper successfully parses the response body as JSON, it validates that the decoded value is a non-null, non-array object before returning it as Record<string, unknown>. If the Cave endpoint legitimately answered 2xx with null, a top-level array, a string, or a number, this CaveRequestError is thrown to enforce the SDK's contract that every JSON-object endpoint returns an object.
Solutions
- Log err.status and err.path, then fetch the endpoint manually to see the actual JSON shape
- Upgrade or align SDK and Cave server versions so the response envelope matches what the SDK expects
- If you run a mock Cave, return object-wrapped payloads (e.g. { items: [...] }, { report: ... }) not bare arrays/scalars
- Wrap calls in try/catch on CaveRequestError and fall back to raw HTTP inspection for debugging
Example fix
// before
// mock server returns: [{ id: 'a1' }]
// after
// mock server returns: { items: [{ id: 'a1' }] } Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight shape probe:
const res = await fetch(baseURL + '/sdk/v1/health');
const body = await res.json();
if (body === null || typeof body !== 'object' || Array.isArray(body)) throw new Error('endpoint not returning JSON objects'); Type guard
const isJsonObject = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
Try / catch
try { const r = await sdk.call(); } catch (e) { if (e instanceof CaveRequestError && e.message.includes('must be a JSON object')) log.error(`version skew: unexpected envelope at ${e.path}`); throw e; } Prevention
- Pin SDK and Cave server versions together and upgrade in lockstep
- Add a contract test asserting each endpoint's response is a JSON object
- Ensure mocks return object-wrapped payloads, never bare arrays/scalars
- Document response envelopes when changing server endpoints
When it happens
Trigger: A Cave server version that returns an array for a collection endpoint the SDK expects to be an object wrapper ({ items: [...] }), a proxy serving JSON scalars/null, or a misrouted request hitting an endpoint that returns a bare JSON literal with 200.
Common situations: Client/server version skew after a server upgrade changed the response envelope; custom or mocked Cave service returning arrays directly; hitting a health-check route that answers "ok" (JSON string) with 200.
Related errors
- cave_output_schema_invalid_json
- cave_output_schema_invalid_json
- cave_output_schema_mismatch
- contents
- githubapp: decode installation
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/44e697f599351214.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/index.ts:2138
const response = await caveFetch(cave, `${cave.options.baseURL}${path}`, init);
if (!response.ok) {
discardResponse(response);
throw new CaveRequestError(response.status, path, `cave request failed (${response.status})`);
}
let decoded: unknown;
try {
if (typeof response.text === "function") {
decoded = JSON.parse(await response.text());
} else if (typeof response.json === "function") {
decoded = await response.json();
} else {
throw new Error("missing response decoder");
}
} catch {
throw new CaveRequestError(response.status, path, "cave response was not valid JSON");
}
if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded)) {
throw new CaveRequestError(response.status, path, "cave response must be a JSON object");
}
return decoded as Record<string, unknown>;
}
async function artifactGet(cave: Cave, artifactId: string, workflow: string, trace: TraceContext): Promise<unknown> {
if (typeof artifactId !== "string" || artifactId.trim() === "") throw new Error("artifactId is required");
const path = `/sdk/v1/artifacts/${encodeURIComponent(artifactId)}`;
const response = await caveFetch(cave, `${cave.options.baseURL}${path}`, {
method: "GET",
headers: headers(cave, workflow, undefined, undefined, trace)
});
if (!response.ok) {
discardResponse(response);
throw new CaveRequestError(response.status, path, `cave request failed (${response.status})`);
}
try {
return JSON.parse(await response.text());
} catch {View on GitHub (pinned to 3ee70a1026)