Skip to content

fix: make discardBody safe when response body lacks dump - #7

Closed
Indosaram wants to merge 1 commit into
code-yeongyu:mainfrom
Indosaram:fix/discard-body-without-dump
Closed

Indosaram wants to merge 1 commit into
code-yeongyu:mainfrom
Indosaram:fix/discard-body-without-dump

Conversation

@Indosaram

@Indosaram Indosaram commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Running the extension inside a host whose module resolution maps undici to a build where request() bodies lack dump() crashed the entire host process with:

TypeError: body.dump is not a function. (In 'body.dump({ limit: 1024 })', 'body.dump' is undefined)
      at discardBody (src/webfetch/fetcher.ts:213:14)

Two defects combined here:

  1. discardBody assumed body.dump() always exists. In some runtimes (bundled loaders, alternate undici resolutions) the response body is a plain stream without it, so discarding a redirect or oversized body throws.
  2. The existing fallback called body.destroy(error), re-emitting the caught error on a stream that has no error listener. An unhandled stream error event escapes as an uncaught exception and kills the host agent process instead of failing a single tool call.

Fix

  • Make dump optional on ResponseBodyStream and feature-check before calling it.
  • When dump is unavailable, drain the body via async iteration bounded by MAX_RESPONSE_SIZE_BYTES.
  • Swallow discard failures (best-effort by definition) and destroy the stream quietly: attach a no-op error listener before destroy() so nothing can escape as unhandled.

Testing

  • New test/discard-body.test.ts: body without dump drains and resolves; mid-stream failure resolves without surfacing; dump-capable body still uses dump.
  • npm run check and full npm test pass (43 tests).

Summary by cubic

Prevents host crashes by making discardBody safe when response bodies lack dump in environments resolving undici to builds without it. Previously it called dump() and then destroy(error), causing a TypeError or an unhandled stream error; now it feature-checks dump, drains with a size cap, and destroys quietly.

  • Uses body.dump({ limit: 1024 }) when available; otherwise drains via async iteration bounded by MAX_RESPONSE_SIZE_BYTES.
  • Swallows discard errors; attaches a no-op error listener before destroy() to avoid uncaught exceptions.
  • No behavior change when dump exists; only failure handling differs.
  • Adds tests for bodies without dump, mid-stream failures, and dump-capable bodies.

Written for commit 8a4743d. Summary will update on new commits.

Review in cubic

Some runtimes resolve undici to builds whose request() body has no
dump(). discardBody then threw TypeError and its fallback
body.destroy(error) re-emitted the error on a stream without an error
listener, crashing the host process with uncaughtException.

Use dump when available, otherwise drain the stream with a size cap.
Discard failures are swallowed and the stream is destroyed quietly.
@code-yeongyu

Copy link
Copy Markdown
Owner

Thank you, @Indosaram — this caught the remaining lifecycle edge beyond the initial Bun compatibility fix. I adapted the bounded drain + error-guard approach into Senpi in code-yeongyu/senpi#1094, with explicit credit in both the PR and changelog. The Senpi adaptation also extracts response-body cleanup into its own focused module to keep the vendored fetcher below our 250 pure-LOC ceiling. Local RED→GREEN, focused tests, root checks, and the real Bun redirect reproduction are green; Senpi CI/review is running now. 🙌

MoerAI pushed a commit to MoerAI/senpi that referenced this pull request Aug 23, 2026
Adapt the lifecycle hardening proposed by @Indosaram in code-yeongyu/pi-webfetch#7 while preserving Senpi's bounded Undici dump path. Bodies without dump() are drained before quiet destruction, and discard-time stream errors are guarded from escaping the process.

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against current main (which already contains #8 / 6e963d0).

The dump() feature-detect is already on main. The remaining unique value is the no-dump drain fallback and the unit tests. I will land that remainder (with two mechanical adjustments below) and close this PR with credit.

Findings:

  • src/webfetch/fetcher.ts:226 (finally in discardBody): the body.on?.("error", …) listener is attached after dump() / drainBody(). A stream that errors during discard can emit before the listener exists. Attach the no-op listener before dump/drain, then destroy() in finally.
  • src/webfetch/fetcher.ts:232 (drainBody): the for await loop does not honor AbortSignal. Discard during timeout/abort can keep the iterator alive until MAX_RESPONSE_SIZE_BYTES. Race the iterator against abort, matching the fetch read path.
  • src/webfetch/fetcher.ts:48 (ResponseBodyStream): making dump optional is correct (already on main). Keep on? / once? optional so Bun-compatible bodies without an EventEmitter API still type-check.

test/discard-body.test.ts is the right regression surface; I will keep those cases (no-dump drain, mid-stream error swallowed, dump path used when present).

@code-yeongyu

Copy link
Copy Markdown
Owner

Superseded by #10 (merged in 169155b), which lands the remaining drain fallback from this PR: feature-detect dump(), drain when dump is missing, attach a quiet error listener before teardown, honor AbortSignal, and add discard-body regression tests. Credit to @Indosaram (Co-authored-by).

@code-yeongyu

Copy link
Copy Markdown
Owner

Closed as superseded by #10 (merged in 169155b).

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.

2 participants