Skip to content

Conversation

@ardatan
Copy link
Owner

@ardatan ardatan commented Sep 30, 2025

Fixes graphql-hive/graphql-yoga#3316 (comment)
Fixes #2709

While iterating headers, if the value is an array like set-cookie, handle it correctly.
Also do not set setCookies multiple times while passing headers to uWebSockets.js

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Correctly handle multiple Set-Cookie headers with uWebSockets.js.
    • Preserve all Set-Cookie values during header initialization across adapters.
    • Improve Node.js compatibility by avoiding bulk header setting on versions affected by duplicate header issues.
  • Tests

    • Added cross-adapter tests verifying multiple Set-Cookie handling and response integrity.
  • Chores

    • Updated dependencies with patch releases to improve stability.

Walkthrough

Adjusts Headers initialization to correctly accumulate Set-Cookie values, prevents duplicate Set-Cookie emission in the uWebSockets adapter, adds cross-adapter cookie tests, and includes a changeset documenting dependency patch updates and the bug-fix. (30 words)

Changes

Cohort / File(s) Summary
Headers set-cookie normalization
packages/node-fetch/src/Headers.ts
Ensure set-cookie values are accumulated correctly from array / Headers-like / object initializers by spreading arrays, pushing single non-null values, and avoiding undefined pushes.
uWebSockets Set-Cookie handling
packages/server/src/uwebsockets.ts
Add an isSetCookieHandled guard inside the corked header population block and skip subsequent set-cookie headers after the first to avoid duplicate emission.
Cookie tests across adapters
packages/server/test/cookies.spec.ts
New test asserting multiple Set-Cookie headers are preserved (via getSetCookie()) across fetch/server adapters; skips hapi due to its behavior.
Changeset and dependency bumps
.changeset/smooth-worlds-pull.md
Add changeset noting patch bumps for @whatwg-node/node-fetch and @whatwg-node/server and the uWebSockets Set-Cookie handling fix.
Node response header path selection
packages/server/src/utils.ts
Restrict use of the setHeaders fast-path by requiring serverResponse.setHeaders and that Node.js version does not start with '1'; fall back to per-header setting otherwise.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Yoga
  participant uwsAdapter
  participant uWS

  Client->>Yoga: HTTP Request
  Yoga->>uwsAdapter: Response (headers + body)
  rect rgba(200,230,255,0.18)
    note over uwsAdapter: Iterate response headers inside cork
    uwsAdapter->>uwsAdapter: isSetCookieHandled = false
    alt header == "set-cookie" and not handled
      uwsAdapter->>uWS: write each cookie value (iterate array)
      uwsAdapter->>uwsAdapter: isSetCookieHandled = true
    else header == "set-cookie" and already handled
      uwsAdapter->>uwsAdapter: skip header
    else
      uwsAdapter->>uWS: write header normally
    end
  end
  uWS-->>Client: HTTP Response
Loading
sequenceDiagram
  participant Caller as Headers.init(...)
  participant Headers

  rect rgba(220,255,220,0.18)
    note over Headers: Build internal header map from various initializers
    Caller->>Headers: entries (array | Headers-like | object)
    alt value is Array
      Headers->>Headers: push(...values) for "set-cookie"
    else value is non-null
      Headers->>Headers: push(single value)
    else
      Headers->>Headers: skip undefined/null
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • louy
  • dotansimha

Poem

I hop where cookies once would clone,
I gather values, not a one alone.
I guard the write, I tidy arrays,
Tests nibble carrots through the maze.
A rabbit cheers the patched-up zone. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR includes changes in the project’s changeset file for dependency version bumps and a Node.js version guard in the sendNodeResponse utility that are not described in the linked issues and appear unrelated to fixing duplicate set-cookie headers. These alterations extend beyond the stated objectives of preventing cookie duplication. Consider extracting the dependency version updates and the Node.js version guard in sendNodeResponse into a separate release or documentation PR, keeping this pull request focused solely on the cookie duplication fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly conveys the primary change by indicating that the PR fixes handling of multiple set-cookie headers in both the server and node-fetch packages when integrating with uWebSockets.js. It explicitly references the affected scopes and the core issue of duplicated cookies. This makes the title clear, specific, and aligned with the main change.
Linked Issues Check ✅ Passed The PR implements the required fixes by correctly aggregating multiple set-cookie values in node-fetch, adding a guard in the uWebSockets adapter to prevent duplicated header emissions, and verifying the behavior with new tests, which aligns with the expectations from issues #3316 and #2709.
Description Check ✅ Passed The description directly references the linked issues and summarizes the code changes to correctly handle array header values and prevent multiple set-cookie invocations in uWebSockets.js, clearly relating to the changeset without deviating or being off-topic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-multiple-cookies

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a983c44 and 923716c.

📒 Files selected for processing (1)
  • packages/server/src/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: unit / deno
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 24
  • GitHub Check: unit / node 18
  • GitHub Check: server (undici)
  • GitHub Check: server (ponyfill)
  • GitHub Check: server (uws)
  • GitHub Check: server (native)
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: node-fetch (noConsumeBody)

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@whatwg-node/node-fetch 0.8.1-alpha-20251009163820-923716c6485f7bf24a93cafed705c3fc6bccb2e6 npm ↗︎ unpkg ↗︎
@whatwg-node/server 0.10.13-alpha-20251009163820-923716c6485f7bf24a93cafed705c3fc6bccb2e6 npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/node-fetch results (noConsumeBody)

   ✓ active_handles.................: avg=140.43609 min=75       med=140     max=194      p(90)=162     p(95)=166    
     data_received..................: 24 MB  803 kB/s
     data_sent......................: 16 MB  520 kB/s
     http_req_blocked...............: avg=3.21µs    min=571ns    med=1.54µs  max=4.64ms   p(90)=2.02µs  p(95)=2.33µs 
     http_req_connecting............: avg=1.2µs     min=0s       med=0s      max=4.47ms   p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=18.98ms   min=534.91µs med=18.61ms max=578.76ms p(90)=24.74ms p(95)=26.57ms
       { expected_response:true }...: avg=18.98ms   min=534.91µs med=18.61ms max=578.76ms p(90)=24.74ms p(95)=26.57ms
     http_req_failed................: 0.00%  ✓ 1           ✗ 157448
     http_req_receiving.............: avg=34.24µs   min=7.93µs   med=24.37µs max=14.11ms  p(90)=38.12µs p(95)=46.12µs
     http_req_sending...............: avg=12.17µs   min=3.1µs    med=7.28µs  max=18.79ms  p(90)=10.17µs p(95)=15.47µs
     http_req_tls_handshaking.......: avg=0s        min=0s       med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=18.94ms   min=500.73µs med=18.57ms max=578.49ms p(90)=24.71ms p(95)=26.5ms 
     http_reqs......................: 157449 5246.924467/s
     iteration_duration.............: avg=38.08ms   min=5.77ms   med=36.72ms max=593.57ms p(90)=42.44ms p(95)=47.43ms
     iterations.....................: 78697  2622.545807/s
     vus............................: 99     min=99        max=100 
     vus_max........................: 100    min=100       max=100 

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/node-fetch results (consumeBody)

   ✓ active_handles.................: avg=140.072012 min=13     med=140     max=190      p(90)=162     p(95)=167    
     data_received..................: 23 MB  756 kB/s
     data_sent......................: 15 MB  485 kB/s
     http_req_blocked...............: avg=3.79µs     min=571ns  med=1.53µs  max=9.47ms   p(90)=2.08µs  p(95)=2.39µs 
     http_req_connecting............: avg=1.71µs     min=0s     med=0s      max=9.44ms   p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=20.16ms    min=1.98ms med=19.62ms max=873.38ms p(90)=25.6ms  p(95)=27.51ms
       { expected_response:true }...: avg=20.16ms    min=1.98ms med=19.62ms max=873.38ms p(90)=25.6ms  p(95)=27.51ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 148270
     http_req_receiving.............: avg=36µs       min=9.09µs med=25.07µs max=20.68ms  p(90)=39.73µs p(95)=47.04µs
     http_req_sending...............: avg=12.99µs    min=3.32µs med=7.37µs  max=17.28ms  p(90)=10.33µs p(95)=15.57µs
     http_req_tls_handshaking.......: avg=0s         min=0s     med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=20.11ms    min=1.93ms med=19.58ms max=873.32ms p(90)=25.55ms p(95)=27.43ms
     http_reqs......................: 148270 4941.625136/s
     iteration_duration.............: avg=40.43ms    min=9.81ms med=39.01ms max=893.78ms p(90)=44.66ms p(95)=49.84ms
     iterations.....................: 74113  2470.07934/s
     vus............................: 91     min=91        max=100 
     vus_max........................: 100    min=100       max=100 

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/server results (native)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 197592     ✗ 0    
     data_received..................: 20 MB   655 kB/s
     data_sent......................: 15 MB   491 kB/s
     http_req_blocked...............: avg=1.54µs   min=892ns    med=1.52µs   max=292.84µs p(90)=1.92µs   p(95)=2.1µs   
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=143.14µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=229.05µs min=167.85µs med=217.53µs max=62.14ms  p(90)=246.28µs p(95)=258.75µs
       { expected_response:true }...: avg=229.05µs min=167.85µs med=217.53µs max=62.14ms  p(90)=246.28µs p(95)=258.75µs
     http_req_failed................: 0.00%   ✓ 0          ✗ 98796
     http_req_receiving.............: avg=25.77µs  min=14.05µs  med=24.52µs  max=2.74ms   p(90)=29.78µs  p(95)=33.02µs 
     http_req_sending...............: avg=9.07µs   min=5.49µs   med=9.19µs   max=159.8µs  p(90)=11µs     p(95)=12.54µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=194.2µs  min=143.05µs med=183.81µs max=62.07ms  p(90)=210.05µs p(95)=221.47µs
     http_reqs......................: 98796   3293.09646/s
     iteration_duration.............: avg=299.11µs min=230.09µs med=286.23µs max=62.29ms  p(90)=318.54µs p(95)=335.63µs
     iterations.....................: 98796   3293.09646/s
     vus............................: 1       min=1        max=1  
     vus_max........................: 1       min=1        max=1  

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/server results (uws)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 250486      ✗ 0     
     data_received..................: 21 MB   697 kB/s
     data_sent......................: 19 MB   622 kB/s
     http_req_blocked...............: avg=1.52µs   min=942ns    med=1.47µs   max=282.1µs  p(90)=1.9µs    p(95)=2.07µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=135.84µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=165.63µs min=107.82µs med=157.05µs max=12.97ms  p(90)=178.2µs  p(95)=185.96µs
       { expected_response:true }...: avg=165.63µs min=107.82µs med=157.05µs max=12.97ms  p(90)=178.2µs  p(95)=185.96µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 125243
     http_req_receiving.............: avg=23.23µs  min=11.93µs  med=21.98µs  max=2.69ms   p(90)=27.48µs  p(95)=30.14µs 
     http_req_sending...............: avg=8.96µs   min=5.54µs   med=9.08µs   max=327.21µs p(90)=11.09µs  p(95)=13.09µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=133.43µs min=84.78µs  med=125.58µs max=12.89ms  p(90)=144.39µs p(95)=151.62µs
     http_reqs......................: 125243  4174.629364/s
     iteration_duration.............: avg=235µs    min=169.96µs med=225.28µs max=13.11ms  p(90)=250.87µs p(95)=261.15µs
     iterations.....................: 125243  4174.629364/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/server results (undici)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 180328      ✗ 0    
     data_received..................: 18 MB   598 kB/s
     data_sent......................: 13 MB   448 kB/s
     http_req_blocked...............: avg=1.63µs   min=972ns    med=1.61µs   max=170.5µs  p(90)=1.99µs   p(95)=2.17µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=114.69µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=257.99µs min=192.93µs med=243.13µs max=94.04ms  p(90)=275.97µs p(95)=290.95µs
       { expected_response:true }...: avg=257.99µs min=192.93µs med=243.13µs max=94.04ms  p(90)=275.97µs p(95)=290.95µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 90164
     http_req_receiving.............: avg=26.24µs  min=14.35µs  med=24.75µs  max=2.73ms   p(90)=30.85µs  p(95)=34.01µs 
     http_req_sending...............: avg=9.44µs   min=5.61µs   med=9.49µs   max=323.71µs p(90)=11.47µs  p(95)=13.32µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=222.3µs  min=166.36µs med=208.28µs max=93.95ms  p(90)=238.89µs p(95)=252.56µs
     http_reqs......................: 90164   3005.348746/s
     iteration_duration.............: avg=328.24µs min=254.7µs  med=312.29µs max=94.2ms   p(90)=349.37µs p(95)=367.79µs
     iterations.....................: 90164   3005.348746/s
     vus............................: 1       min=1         max=1  
     vus_max........................: 1       min=1         max=1  

@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

@benchmarks/server results (ponyfill)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 242554      ✗ 0     
     data_received..................: 24 MB   796 kB/s
     data_sent......................: 18 MB   602 kB/s
     http_req_blocked...............: avg=1.62µs   min=932ns    med=1.55µs   max=190.83µs p(90)=2.02µs   p(95)=2.23µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=133.05µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=170.56µs min=117.56µs med=164.13µs max=5.84ms   p(90)=185.87µs p(95)=194.07µs
       { expected_response:true }...: avg=170.56µs min=117.56µs med=164.13µs max=5.84ms   p(90)=185.87µs p(95)=194.07µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 121277
     http_req_receiving.............: avg=24.83µs  min=13.53µs  med=23.52µs  max=3.36ms   p(90)=28.73µs  p(95)=31.99µs 
     http_req_sending...............: avg=9.31µs   min=5.6µs    med=9.35µs   max=338.28µs p(90)=11.96µs  p(95)=13.7µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=136.41µs min=92.63µs  med=130.65µs max=5.79ms   p(90)=150.45µs p(95)=157.8µs 
     http_reqs......................: 121277  4042.427997/s
     iteration_duration.............: avg=242.65µs min=176.17µs med=234.91µs max=6.24ms   p(90)=260.78µs p(95)=272.16µs
     iterations.....................: 121277  4042.427997/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/server/test/cookies.spec.ts (1)

8-10: Consider adding more context to the hapi skip comment.

While the skip is acceptable for this PR scope (focused on uWebSockets.js), consider adding a link to an issue or brief explanation of what "issues" hapi has with multiple Set-Cookie headers for future maintainers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62876e5 and 8b334ac.

📒 Files selected for processing (4)
  • .changeset/smooth-worlds-pull.md (1 hunks)
  • packages/node-fetch/src/Headers.ts (3 hunks)
  • packages/server/src/uwebsockets.ts (1 hunks)
  • packages/server/test/cookies.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/server/test/cookies.spec.ts (2)
packages/server/test/test-fetch.ts (1)
  • runTestsForEachFetchImpl (15-102)
packages/server/test/test-server.ts (1)
  • runTestsForEachServerImpl (494-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: unit / node 24
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: unit / bun
  • GitHub Check: unit / deno
  • GitHub Check: server (native)
  • GitHub Check: server (uws)
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: server (undici)
  • GitHub Check: server (ponyfill)
🔇 Additional comments (6)
packages/server/src/uwebsockets.ts (1)

234-251: Correct fix for set-cookie duplication bug.

The guard prevents multiple invocations of getSetCookie() during header iteration. When the Headers polyfill yields multiple set-cookie entries (one per cookie), this fix ensures getSetCookie() is called only once, preventing the exponential duplication reported in the linked issues.

packages/node-fetch/src/Headers.ts (3)

80-85: LGTM! Correctly handles array-valued set-cookie headers.

The array spread and null-check ensure multiple cookie values are accumulated properly during initialization.


94-99: LGTM! Correctly handles array-valued set-cookie headers.

The logic mirrors the array-based initialization path and correctly accumulates cookie values while preventing them from being added to the regular header map.


111-116: LGTM! Correctly handles array-valued set-cookie headers.

The logic is sound: array values are spread, single values are pushed. The outer null-check at line 107 ensures no null/undefined values are processed.

.changeset/smooth-worlds-pull.md (1)

1-6: LGTM! Changeset correctly documents the bug fix.

The description accurately reflects the changes made to handle multiple set-cookie headers in the uWebSockets.js integration.

packages/server/test/cookies.spec.ts (1)

11-25: LGTM! Test comprehensively validates the fix.

The test correctly verifies that:

  • Two distinct set-cookie headers are preserved without duplication
  • Cookie attributes (SameSite, Secure) are maintained
  • The fix works across all supported fetch and server implementations (except hapi)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server/src/utils.ts (1)

307-326: Bug: drops additional Set-Cookie values when Headers.getSetCookie is unavailable

In the per-header fallback, if getSetCookie is missing, only the first set-cookie value is written; subsequent ones are skipped due to setCookiesSet short-circuit. This loses cookies on fetch impls that don’t expose getSetCookie but do iterate multiple set-cookie entries.

Fix by accumulating values and setting set-cookie once after the loop.

-    // @ts-expect-error - setHeaders exist
-    if (serverResponse.setHeaders && globalThis.process?.versions?.node?.startsWith('2')) {
+    // @ts-expect-error - setHeaders exist
+    if (serverResponse.setHeaders && globalThis.process?.versions?.node?.startsWith('2')) {
       // @ts-expect-error - setHeaders exist
       serverResponse.setHeaders(fetchResponse.headers);
     } else {
-      let setCookiesSet = false;
-      fetchResponse.headers.forEach((value, key) => {
-        if (key === 'set-cookie') {
-          if (setCookiesSet) {
-            return;
-          }
-          setCookiesSet = true;
-          const setCookies = fetchResponse.headers.getSetCookie?.();
-          if (setCookies) {
-            serverResponse.setHeader('set-cookie', setCookies);
-            return;
-          }
-        }
-        serverResponse.setHeader(key, value);
-      });
+      let setCookiesSet = false;
+      let setCookiesFallback: string[] | null = null;
+      fetchResponse.headers.forEach((value, key) => {
+        if (key === 'set-cookie') {
+          if (setCookiesSet) {
+            // accumulate additional values for fallback
+            if (setCookiesFallback) setCookiesFallback.push(value);
+            return;
+          }
+          setCookiesSet = true;
+          const setCookies = fetchResponse.headers.getSetCookie?.();
+          if (setCookies?.length) {
+            serverResponse.setHeader('set-cookie', setCookies);
+            return;
+          }
+          // Fallback: collect values seen during iteration, set after loop
+          setCookiesFallback = [value];
+          return;
+        }
+        serverResponse.setHeader(key, value);
+      });
+      if (setCookiesFallback?.length) {
+        serverResponse.setHeader('set-cookie', setCookiesFallback);
+      }
     }
🧹 Nitpick comments (2)
packages/server/src/utils.ts (1)

307-310: Make Node version gating future-proof

startsWith('2') will disable setHeaders on Node 30+ and matches the hypothetical Node 2.x. Parse the major version numerically instead.

-    if (serverResponse.setHeaders && globalThis.process?.versions?.node?.startsWith('2')) {
+    const nodeMajor =
+      Number(globalThis.process?.versions?.node?.split?.('.')?.[0] ?? 0);
+    if (serverResponse.setHeaders && nodeMajor >= 20) {
packages/server/test/cookies.spec.ts (1)

21-24: Avoid ordering assumptions in cookie assertions

Different adapters may emit multiple Set-Cookie headers in different orders. Assert membership and length to reduce flakiness.

-        expect(cookies).toEqual([
-          'name=value0; SameSite=None; Secure',
-          'name=value1; SameSite=Strict; Secure',
-        ]);
+        expect(cookies).toHaveLength(2);
+        expect(cookies).toEqual(
+          expect.arrayContaining([
+            'name=value0; SameSite=None; Secure',
+            'name=value1; SameSite=Strict; Secure',
+          ]),
+        );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b334ac and 2ff4979.

📒 Files selected for processing (2)
  • packages/server/src/utils.ts (1 hunks)
  • packages/server/test/cookies.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/server/test/cookies.spec.ts (2)
packages/server/test/test-fetch.ts (1)
  • runTestsForEachFetchImpl (15-102)
packages/server/test/test-server.ts (1)
  • runTestsForEachServerImpl (494-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: unit / node 24
  • GitHub Check: unit / node 18
  • GitHub Check: unit / node 20
  • GitHub Check: unit / deno
  • GitHub Check: unit / bun
  • GitHub Check: server (undici)
  • GitHub Check: server (native)
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: server (uws)
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: server (ponyfill)
🔇 Additional comments (1)
packages/server/test/cookies.spec.ts (1)

11-18: Nice: minimal, adapter-agnostic repro

Using Response + headers.append twice mirrors real usage and validates cross-adapter behavior. LGTM.

@ardatan ardatan force-pushed the fix-multiple-cookies branch from 2ff4979 to a983c44 Compare October 9, 2025 16:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff4979 and a983c44.

📒 Files selected for processing (5)
  • .changeset/smooth-worlds-pull.md (1 hunks)
  • packages/node-fetch/src/Headers.ts (3 hunks)
  • packages/server/src/utils.ts (1 hunks)
  • packages/server/src/uwebsockets.ts (1 hunks)
  • packages/server/test/cookies.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/server/test/cookies.spec.ts
  • packages/server/src/uwebsockets.ts
🔇 Additional comments (2)
packages/node-fetch/src/Headers.ts (1)

80-84: LGTM! Set-Cookie array handling is correct.

The changes properly handle both array and single-value set-cookie headers across all three initialization paths (array-based, Headers-like, and plain object). The null checks prevent undefined values from being pushed, and spreading array values ensures multiple cookies are accumulated correctly.

Also applies to: 94-98, 111-114

.changeset/smooth-worlds-pull.md (1)

1-6: LGTM! Changeset accurately documents the fix.

The patch-level designation and description appropriately capture the scope of the bug fix for multiple set-cookie headers with uWebSockets.js integration.

Copy link
Collaborator

@enisdenjo enisdenjo left a comment

Choose a reason for hiding this comment

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

Aside from the coderabbit comment, lgtm!

@ardatan ardatan merged commit 93f2932 into master Oct 9, 2025
25 checks passed
@ardatan ardatan deleted the fix-multiple-cookies branch October 9, 2025 16:40
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.

uWebSockets duplicate set-cookie responses uWebSockets duplicate set-cookie responses

3 participants