Skip to content

fix(adapter): read the rest of a body its answer left unread, or close the connection - #439

Merged
k-taro56 merged 2 commits into
mainfrom
fix/native-unread-request-body
Nov 3, 2026
Merged

k-taro56 merged 2 commits into
mainfrom
fix/native-unread-request-body

Conversation

@k-taro56

@k-taro56 k-taro56 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • The native server gave the runtime Readable.toWeb(req) as the request body. When the application stopped reading a body, the request was left paused, or destroyed with its connection detached. Either way, Node.js stopped reading the connection.
    • This happened to a Server Action over its size limit, to a middleware's side of a split body that nothing read, and to a POST to a static page.
    • The connection then answered nothing more until its keep-alive timeout.
  • The body is now read from the request directly.
    • Cancelling the body stops the reading and leaves the request alone.
    • Once the answer has gone out, the rest is read and dropped within workerd's grace: 64 KiB and 1 s, from KJ's HttpServerSettings.
    • Past either limit, the connection is ended, then dropped 500 ms later so the client can read its answer first.
    • A read of the body after its answer fails, as it does in workerd.

How it showed up

In the 16.4.3 deploy-tests nightly (run 41491299214), app-action-size-limit-invalid and its node-middleware variant timed out on node, once on Node.js 24 and once on 26.

Verification

  • The new native-request-body.test.ts has 6 cases. 4 of them fail on main: the connection is not closed after the answer, a slow remainder is not bounded, a cancelled body leaves the connection stuck, and a late read succeeds.

  • The upstream suite's sequence (the 3 MB plaintext action, then /form) was driven with Playwright against a local build. All runs used a keep-alive timeout of 10 minutes:

    Server Page loads that hung Connections left
    upwind start, main 1 of 6 stuck ones
    upwind start, this PR 0 of 20 none
    next start 0 of 8 —
  • The tests ran on Node.js 24.22.1 and 26.13.0.

Not in this PR

The private bridge between the runtime and Node.js handlers leaves the same kind of connection behind.

That is #440.

…e the connection

The native server handed the runtime `Readable.toWeb(req)`. A body the
application stopped reading left the request paused, or destroyed with its
connection detached, and in both cases Node.js stopped reading the connection.
That happened to a Server Action over its size limit, to a middleware's side of
a split body nobody read, and to a POST to a static page. The connection then
answered nothing more until its keep-alive timeout. A client that sent its next
request on it waited that long: ten minutes with `--keepAliveTimeout 600000`.
A slow upload stalled until `requestTimeout`.

The body is now read from the request directly. Cancelling it stops the
reading and leaves the request alone. Once the answer has gone out, the rest is
read and dropped within workerd's grace (64 KiB, 1 s). Past either, the
connection is ended and dropped 500 ms later. A read of the body after its
answer fails, as it does in workerd.
@arkor-deploy

arkor-deploy Bot commented Nov 2, 2026

Copy link
Copy Markdown

Arkor — Preview deployments for arkorlab-upwind

Use the checkbox below to choose whether this pull request gets preview deployments.

  • Build and deploy previews for this pull request

Checking this builds the latest commit and future pushes. Unchecking stops queued and future builds; a running build can finish and an existing preview stays available.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T01:59:55.349349Z 036044d Manual request
🔒 Security Review ⚠️ Failed 2026-11-02T19:25:43.231433Z
We couldn’t complete this review. Comment @codex security review to try again.
036044d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 36 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e103328b-d853-4428-a402-4da271581386
Commits

Reviewing files that changed from the base of the PR and between 2d305b0 and 036044d.

Files selected for processing: 2
  • packages/adapter/src/native/request-body.ts
  • packages/adapter/test/native-request-body.test.ts
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cbdf4b9e-e7ac-43be-9329-da7626fba275
Commits

Reviewing files that changed from the base of the PR and between 541cd93 and 2d305b0.

Files selected for processing: 3
  • packages/adapter/src/native/http.ts
  • packages/adapter/src/native/request-body.ts
  • packages/adapter/test/native-request-body.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Native HTTP requests now expose non-GET/HEAD bodies through a stream with backpressure and cancellation handling. When a response finishes before the request body is complete, the stream errors and remaining data is drained within configured limits.

Changes: Native request body handling

Layer / File(s) Summary
Request body stream and cleanup
packages/adapter/src/native/request-body.ts
nativeBody streams request chunks, handles stream and request lifecycle events, and drains unread data within byte and time limits before closing the connection.
Adapter integration and lifecycle tests
packages/adapter/src/native/http.ts, packages/adapter/test/native-request-body.test.ts
nativeRequest uses nativeBody for non-GET/HEAD methods. Tests cover body reads, cancellation, socket reuse, and connection closure.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Adapter as Native HTTP adapter
  participant Body as nativeBody
  participant Request as IncomingMessage
  participant App as Application
  participant Response as ServerResponse
  participant Socket
  Client->>Adapter: Send HTTP request
  Adapter->>Body: Create body stream for non-GET/HEAD
  Request->>Body: Deliver request chunks
  App->>Body: Read or cancel body stream
  Response-->>Body: Emit finish before request completes
  Body->>App: Error an open stream
  Body->>Request: Drain remaining request data
  Body->>Socket: End socket if drain grace expires
Loading
Merge Risk: Low · previous review 2d305

A cancelled upload can keep consuming data while the application prepares its response. This is a bounded merge risk that merits a pause-on-cancel fix.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change: the adapter now reads the unread remainder of a request body after the response, or closes the connection when the drain limits are exceeded. The raw summary and objec…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: the adapter now reads the unread remainder of a request body after the response, or closes the connection when the drain limits are exceeded. The raw summary and objectives both describe this behavior in packages/adapter/src/native/request-body.ts and http.ts, so the title is fully related to the changeset. It is specific and conveys the primary fix.

Full details: Docstring Coverage

Explanation

Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 4 functions across 3 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@greptile-apps

greptile-apps Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; both previous findings are fixed, and no new actionable issues were found.

Summary

The PR replaces Readable.toWeb(req) with a body stream that leaves the connection available after cancellation.

  • The native server drains small unread bodies to keep connections moving.

Reviews (2) · Last reviewed commit: "fix(adapter): fail a late read of any un..." · Reviewed by Greptile

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  • Fix comments: Fix CodeRabbit comments on this PR

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/adapter/src/native/request-body.ts">

<violation number="1" location="packages/adapter/src/native/request-body.ts:82">
P2: Cancelling after the body has started flowing does not stop Node from reading it. Pause the request before removing the listener so cancellation preserves the bounded drain behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

};
const shut = (): void => {
open = false;
req.off('data', onData);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Cancelling after the body has started flowing does not stop Node from reading it. Pause the request before removing the listener so cancellation preserves the bounded drain behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/adapter/src/native/request-body.ts, line 82:

<comment>Cancelling after the body has started flowing does not stop Node from reading it. Pause the request before removing the listener so cancellation preserves the bounded drain behavior.</comment>

<file context>
@@ -0,0 +1,117 @@
+  };
+  const shut = (): void => {
+    open = false;
+    req.off('data', onData);
+  };
+  req.pause();
</file context>
Suggested change
req.off('data', onData);
req.pause();
req.off('data', onData);

…led one

- The body stream queued up to 64 KiB ahead of the application. A small
  body could therefore be read whole into its queue and closed, and a read
  after the answer still succeeded. The stream now queues nothing ahead of
  the application's reads.
- At the answer's end, a body left open fails its readers whatever was
  received. A body received whole is only dropped, because the connection
  already reads its next request.
- `req.read(0)` marks the body as begun. Otherwise Node.js reads a body
  nothing has read to its end before the next request, with no bound.
- Cancelling now pauses the request. Without a 'data' listener, a flowing
  request kept reading its connection, past the grace.
- The test of a stalled body bounds its close at 3 s.
@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@soleil-colza

Copy link
Copy Markdown
Contributor

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 036044db92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@k-taro56
k-taro56 merged commit 4d0d61c into main Nov 3, 2026
124 checks passed
@k-taro56
k-taro56 deleted the fix/native-unread-request-body branch November 3, 2026 02:11
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