{"record":{"id":"56bfd604b6c1ca23","repo":"ruvnet/ruflo","slug":"received-empty-response-from-url-but-allownull","errorCode":null,"errorMessage":"Received empty response from ${url} but allowNull is not set to true","messagePattern":"Received empty response from (.+?) but allowNull is not set to true","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"ruflo/src/ruvocal/src/lib/utils/fetchJSON.ts","lineNumber":19,"sourceCode":"export async function fetchJSON<T>(\n\turl: string,\n\toptions?: {\n\t\tfetch?: typeof window.fetch;\n\t\tallowNull?: boolean;\n\t}\n): Promise<T> {\n\tconst response = await (options?.fetch ?? fetch)(url);\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n\t}\n\n\t// Handle empty responses (which parse to null)\n\tconst text = await response.text();\n\tif (!text || text.trim() === \"\") {\n\t\tif (options?.allowNull) {\n\t\t\treturn null as T;\n\t\t}\n\t\tthrow new Error(`Received empty response from ${url} but allowNull is not set to true`);\n\t}\n\n\treturn JSON.parse(text);\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/ruflo/src/ruvocal/src/lib/utils/fetchJSON.ts#L1-L24","documentation":"Thrown by fetchJSON when the response was OK (2xx) but the body parsed to empty (no text or whitespace-only). Because the generic return type T is non-null, a null result is only allowed when the caller explicitly opts in via options.allowNull === true; otherwise the helper refuses to return null as T.","triggerScenarios":"An endpoint returns 200 with an empty body (or 204 No Content with no body) — common for DELETE, ack-style POSTs, or a misconfigured proxy that strips the body. The caller did not pass allowNull: true.","commonSituations":"Calling a REST endpoint that legitimately returns 204; a backend that returns 200 + empty when a resource is not found instead of a real 404; switching fetchJSON to point at an endpoint whose contract is \"empty on success\".","solutions":["If an empty/204 response is valid for this call, pass { allowNull: true } and handle T | null at the call site.","If the endpoint should return JSON, fix the server to return a real body (or a proper 404).","Check for an intercepting proxy (nginx try_files, a CDN) that is replacing the body with an empty page.","Confirm Content-Type and that no middleware is converting 204 into 200-with-empty."],"exampleFix":"// before\nconst data = await fetchJSON<MyType>(`${base}/api/thing/${id}`);\n// after\nconst data = await fetchJSON<MyType | null>(`${base}/api/thing/${id}`, { allowNull: true });\nif (data === null) return null;","handlingStrategy":"validation","validationCode":"// decide based on the endpoint's contract before calling\nconst allowsEmpty = endpointReturnsEmptyOnSuccess(url);\nconst data = await fetchJSON<T | null>(url, { allowNull: allowsEmpty });\nif (data === null && !allowsEmpty) throw new Error(`unexpected null from ${url}`);","typeGuard":null,"tryCatchPattern":"try {\n  return await fetchJSON<T>(url); // allowNull omitted intentionally\n} catch (e) {\n  if (String((e as Error)?.message ?? \"\").includes(\"allowNull is not set to true\")) {\n    return null as T; // empty body is valid for this caller\n  }\n  throw e;\n}","preventionTips":["Document which endpoints return 204/empty and pass allowNull:true for exactly those.","Prefer fixing the server to return a real JSON body (or a proper 404) over silencing the guard.","In tests, assert that endpoints expected to return data never return an empty body."],"tags":["fetch","validation","http","utility"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}