Skip to content

fix: read HTTP response bodies exactly once#43

Merged
asachs01 merged 1 commit into
mainfrom
fix/single-read-response-body
Jul 18, 2026
Merged

fix: read HTTP response bodies exactly once#43
asachs01 merged 1 commit into
mainfrom
fix/single-read-response-body

Conversation

@asachs01

Copy link
Copy Markdown
Member

Port of the single-read response fix proven in node-connectwise-automate#54 (root cause of connectwise-automate-mcp#54) — API calls returning {} and then failing with Body is unusable: Body has already been read. The same defect existed verbatim in this repo's src/http.ts.

Root cause (src/http.ts handleResponse)

  1. Double body read: the error path did await response.json() (consumes the one-shot body stream, throws on non-JSON) then await response.text() in the catch on the same consumed body → undici's Body is unusable TypeError, masking the real response. Hosted instances front the API with WAF/proxy HTML error pages, so this fires exactly when the diagnostics matter most.
  2. Silent empty success: a 200 whose content-type wasn't application/json returned {} — login pages / WAF challenges read as successful-but-empty API calls.

Fix

Read the body exactly once as text, then parse:

  • JSON parses regardless of the content-type header (some proxies mislabel).
  • Genuinely empty 200/204 keeps the historical {} shape.
  • Non-JSON 200 now raises HaloPsaError with the content-type and a body snippet — surfacing WAF/proxy interference diagnosably instead of pretending success.
  • Error paths get the parsed-or-raw body in their typed errors, never a second read.

src/auth.ts already reads each response exactly once per branch — left untouched.

Verification

  • 7 new tests in tests/unit/http.test.ts using real Response objects so one-shot body-stream semantics are actually exercised — against the unfixed code 4 of them fail with the exact reported Body is unusable TypeError.
  • Full suite 86/86, lint clean, typecheck clean, build clean.

The error path consumed the one-shot Response body with response.json()
and then re-read it with response.text() in the catch, throwing
"Body is unusable: Body has already been read" and masking the real
(often non-JSON WAF/proxy HTML) response. The ok path silently returned
{} for any 200 without a JSON content-type. Port of the fix proven in
node-connectwise-automate (connectwise-automate-mcp#54).
@asachs01
asachs01 merged commit 2333366 into main Jul 18, 2026
2 checks passed
@asachs01
asachs01 deleted the fix/single-read-response-body branch July 18, 2026 15:55
github-actions Bot pushed a commit that referenced this pull request Jul 18, 2026
## [1.0.6](v1.0.5...v1.0.6) (2026-07-18)

### Bug Fixes

* read HTTP response bodies exactly once ([#43](#43)) ([2333366](2333366)), closes [connectwise-automate-mcp#54](https://github.com/connectwise-automate-mcp/issues/54)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant