http-204-empty-body-fetch-json-parse
The dexdat-core repository is not present in this environment, so I couldn't run/tests against the actual code. Instead I wrote a self-contained, directly usable solution.
Deliverable: ~/solution-http-204-empty-body.md
Root cause: request() in frontend/src/lib/api.ts calls res.json() unconditionally on every ok response. The backend DELETE /v1/tags/{id} returns 204 No Content with an empty body (FastAPI status_code=204, None). res.json() on an empty body throws SyntaxError: Unexpected end of JSON input, which the caller's catch renders as a false 'Delete failed' toast even though the delete actually succeeded (retry → 404/409 Tag not found).
Fix: In the shared ApiClient.request(), after the non-ok error path and before res.json(), short-circuit on res.status === 204:
if (res.status === 204) {
return { data: undefined as T, status: res.status };
}
Keyed off res.status, it covers all 204 endpoints (tags, contexts, identities, datapoints, rules deletes) with no per-handler changes. The non-ok/401 path and backend are untouched (204 is the intended contract).
Tests: Two new vitest tests mock a 204 response whose json() rejects (mirroring a real empty-body Response) for DELETE + POST, asserting { data: undefined, status: 204 } and that json() is never called. Expected 44/44 passing.
The solution file includes the exact code edits, the test file additions, and both automated (npx vitest run) and manual verification steps.
If you can point me to a checkout of dexdat-core (e.g. clone it), I can apply the fix and run the test suite to confirm 44/44 live.
# Evidence - Problem class: http-204-empty-body-fetch-json-parse - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-18T01:57:21.510Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "FE fetch client (frontend/src/lib/api.ts request()) called res.json() UNCONDITIONALLY on every successful response. Backend DELETE /v1/tags/{id} returns 204 No Content with an empty body (FastAPI status_code=204, returns None). res.json() on the empty body throws SyntaxError 'Unexpected end of JSON input', which the caller's catch block renders as 'Delete failed' toast even though the resource WAS deleted (retry -> 404/409 'Tag not found'). FIX (dexdat-core DOGFOOD-004, commit d06a9a7): in the shared ApiClient.request(), after the non-ok error path and before res.json(), add: if (res.status === 204) return { data: undefined as T, status: res.status }; \u2014 no JSON parse for empty-body successes. Non-ok error path and 401 refresh logic untouched; backend unchanged (204 is the intended contract). Tests: vitest mocks a 204 response whose json() REJECTS (mirrors real empty-body fetch Response) for DELETE + POST, asserting { data: undefined, status: 204 }; 44/44 pass. Keyed off res.status, so it covers every 204 endpoint (tags, contexts, identities, datapoints, rules deletes) without per-handler changes.", "environment": "production", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "http-204-empty-body-fetch-json-parse", "provider": "openrouter", "solved_at": "2026-08-18T01:57:21.511Z", "version": "nextjs"}The dexdat-core repository is not present in this environment, so I couldn't run/tests against the actual code. Instead I wrote a self-contained, directly usable solution.
Deliverable: ~/solution-http-204-empty-body.md
Root cause: request() in frontend/src/lib/api.ts calls res.json() unconditionally on every ok response. The backend DELETE /v1/tags/{id} returns 204 No Content with an empty body (FastAPI status_code=204, None). res.json() on an empty body throws SyntaxError: Unexpected end of JSON input, which the caller's catch renders as a false 'Delete failed' toast even though the delete actually succeeded (retry → 404/409 Tag not found).
Fix: In the shared ApiClient.request(), after the non-ok error path and before res.json(), short-circuit on res.status === 204:
if (res.status === 204) {
return { data: undefined as T, status: res.status };
}
Keyed off res.status, it covers all 204 endpoints (tags, contexts, identities, datapoints, rules deletes) with no per-handler changes. The non-ok/401 path and backend are untouched (204 is the intended contract).
Tests: Two new vitest tests mock a 204 response whose json() rejects (mirroring a real empty-body Response) for DELETE + POST, asserting { data: undefined, status: 204 } and that json() is never called. Expected 44/44 passing.
The solution file includes the exact code edits, the test file additions, and both automated (npx vitest run) and manual verification steps.
If you can point me to a checkout of dexdat-core (e.g. clone it), I can apply the fix and run the test suite to confirm 44/44 live.
# Evidence - Problem class: http-204-empty-body-fetch-json-parse - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-18T01:57:21.510Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "FE fetch client (frontend/src/lib/api.ts request()) called res.json() UNCONDITIONALLY on every successful response. Backend DELETE /v1/tags/{id} returns 204 No Content with an empty body (FastAPI status_code=204, returns None). res.json() on the empty body throws SyntaxError 'Unexpected end of JSON input', which the caller's catch block renders as 'Delete failed' toast even though the resource WAS deleted (retry -> 404/409 'Tag not found'). FIX (dexdat-core DOGFOOD-004, commit d06a9a7): in the shared ApiClient.request(), after the non-ok error path and before res.json(), add: if (res.status === 204) return { data: undefined as T, status: res.status }; \u2014 no JSON parse for empty-body successes. Non-ok error path and 401 refresh logic untouched; backend unchanged (204 is the intended contract). Tests: vitest mocks a 204 response whose json() REJECTS (mirrors real empty-body fetch Response) for DELETE + POST, asserting { data: undefined, status: 204 }; 44/44 pass. Keyed off res.status, so it covers every 204 endpoint (tags, contexts, identities, datapoints, rules deletes) without per-handler changes.", "environment": "production", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "http-204-empty-body-fetch-json-parse", "provider": "openrouter", "solved_at": "2026-08-18T01:57:21.511Z", "version": "nextjs"}