api.ts: dropped-connection body reads file per-endpoint error tracking issues instead of being classified as NetworkError
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 39.9k
- Forks
- 3.4k
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 232
Description
Bug Description
getJSONFromSuccessResponse in frontend/src/lib/api.ts treats a dropped connection as an application fault, while handleFetch — a few hundred lines down in the same file — correctly classifies the identical failure as a network error. The result is a large, permanently-noisy family of error tracking issues named Failed to read response body [<METHOD> <path>] (status N).
The asymmetry
When the browser tears down a connection mid-request, fetch (or the body read) rejects with a bare TypeError. There are two places that can catch it, depending on whether response headers had already arrived:
- Headers not yet received →
handleFetch(frontend/src/lib/api.ts:7519) catches theTypeError, runsclassifyNetworkFailure(), emitsclient_request_failurewithstatus: 0, and throws aNetworkErrorcarrying the original error ascause.offline/navigatingreasons are then dropped bydropUnactionableNetworkExceptions. - Headers received (2xx), body read fails →
getJSONFromSuccessResponse(frontend/src/lib/api.ts:400-409) catches the sameTypeErrorand throws a bareApiErrorwith no classification, noclient_request_failure, and nocause.
Same underlying event, two different outcomes. The second path files a real error tracking issue every time.
Why it's so noisy
The thrown message embeds the request pathname, so every distinct API route produces its own fingerprint. This one code path has spawned hundreds of separate issues across the app — one per endpoint — and each new endpoint that ships creates a brand-new issue, which fires any "issue created" alerting. Volume skews heavily toward WebKit, consistent with a body-stream teardown rather than a per-endpoint defect. Discarding the original error also means the reports carry nothing useful for triage: a genuinely truncated body and a cancelled request are indistinguishable after the fact.
How to reproduce
Observed in production, single-session evidence:
- Navigate between scenes so several concurrent API requests are in flight.
- Have the connection drop while they are running (all requests on the connection die within the same few ms).
- Requests that had not yet received headers are logged as
client_request_failurewithstatus: 0andfailure_reason: network, and file no issue. - The one request that had received its
200headers surfaces asFailed to read response body [GET /api/...] (status 200)in error tracking, sourced atfrontend/src/lib/api.ts:408.
In the case that prompted this, six requests died in the same ~70ms window alongside an SSE error; five were classified as network failures and the sixth — differing only in having received headers first — became a new error tracking issue.
Proposed fix
Route the body-read failure through the same classification handleFetch already uses:
- In
getJSONFromSuccessResponse, whenresponse.text()rejects with aTypeErrorand it is not anAbortError, callclassifyNetworkFailure(), emitcaptureClientRequestFailure({ status: 0, ... }), and thrownew NetworkError(reason, error)so the original error is preserved ascause. - Keep
ApiErrorfor the genuine defect below it: the body arrived intact but is not valid JSON (Malformed JSON response).
This collapses the per-endpoint fingerprints into a single grouped NetworkError, drops the offline / navigating slice entirely via the existing dropUnactionableNetworkExceptions filter, and preserves the underlying error for the network reason that remains.
Worth adding a test alongside the existing coverage in frontend/src/lib/api.test.ts (see the NetworkError assertions around lines 370 and 423) for the "headers received, body read rejects" case.
Additional context
Raised from a Slack thread: https://posthog.slack.com/archives/C0B1RPLUSRZ/p1787222423292689?thread_ts=1787222423.292689&cid=C0B1RPLUSRZ
Relevant code:
frontend/src/lib/api.ts:400-409—getJSONFromSuccessResponse, the unclassified throwfrontend/src/lib/api.ts:7502-7532—handleFetch, the correct classificationfrontend/src/lib/api-error.ts:170-198—NETWORK_ERROR_MESSAGES,UNACTIONABLE_NETWORK_ERROR_MESSAGES,NetworkErrorfrontend/src/layout/navigation/SelfReadOnlyNotice/selfReadOnlyModeLogic.ts:84-97—dropUnactionableNetworkExceptions
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in frontend/src/lib/api.ts at getJSONFromSuccessResponse and compare its body-read handling with handleFetch around lines 7502-7532. Review NetworkError and classification behavior in frontend/src/lib/api-error.ts, then add coverage in frontend/src/lib/api.test.ts for a rejected body read after headers arrive. Done means the failure is classified consistently, preserves its cause, and retains ApiError for malformed JSON.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100