JuliusBrussee/caveman · warning
missing response decoder
Error message
missing response decoder
What it means
An internal guard inside the shared caveFetch response decoder: if the injected fetch-like Response object exposes neither a .text() nor a .json() function, the code throws this plain Error, which is immediately caught by the surrounding try/catch and rethrown as CaveRequestError("cave response was not valid JSON"). It normally indicates a non-standard fetch implementation (custom mock, polyfill, or undici shim) rather than a server problem.
Solutions
- Make your mock/custom Response implement text() (and/or json()) returning a JSON string or parsed object
- Prefer realistic stubs (e.g. undici MockAgent, nock with proper Response) over hand-rolled objects
- Check the fetch polyfill version and upgrade to one with full Response API support
- Catch the resulting CaveRequestError with message 'cave response was not valid JSON' and inspect the injected fetch
Example fix
// before
fetchMock.mockResolvedValue({ ok: true, status: 200 });
// after
fetchMock.mockResolvedValue({ ok: true, status: 200, text: async () => JSON.stringify({ ok: true }), json: async () => ({ ok: true }) }); Defensive patterns
Strategy: validation
Validate before calling
function isValidFetch(f: unknown): boolean { return typeof f === 'function'; }
// and for the response: typeof (res as Response).text === 'function' || typeof (res as Response).json === 'function' Type guard
function hasDecoder(res: unknown): res is Response { return !!res && typeof (res as Response).text === 'function' && typeof (res as Response).json === 'function'; } Try / catch
try { const r = await sdk.call(); } catch (e) { if (e instanceof CaveRequestError && e.message.includes('not valid JSON')) console.error('check custom fetch Response shape'); throw e; } Prevention
- Use established mocking libs (undici MockAgent, nock) instead of hand-built Response objects
- Ensure every custom fetch wrapper returns a real or fully-shaped Response
- Type the custom fetch as typeof fetch so missing Response methods fail compilation
- Test the full SDK call path, not just fetch, in unit tests
When it happens
Trigger: Passing a custom fetch implementation via the Cave options whose Response objects lack both text and json methods (e.g. a mock returning { ok: true } only, or a Node http callback wrapper that yields a raw stream object).
Common situations: Test suites stubbing global fetch with simplified objects; environments with an incomplete fetch polyfill; wrapping fetch to add metrics but returning a custom response shape.
Related errors
- AbortError
- artifactId is required
- assemble requires model and sessionId
- binary download failed
- bounded_response_unavailable
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/32e5138a8fafb498.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/index.ts:2132
async function request(cave: Cave, path: string, body?: unknown, workflow?: string, trace?: TraceContext, extraHeaders?: Record<string, string>): Promise<Record<string, unknown>> {
const init: RequestInit = {
method: body === undefined ? "GET" : "POST",
headers: { ...headers(cave, workflow ?? cave.options.defaultWorkflow ?? "unlabeled-workflow", undefined, undefined, trace), ...extraHeaders }
};
if (body !== undefined) init.body = JSON.stringify(body);
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) {View on GitHub (pinned to 3ee70a1026)