Skip to content

Conversation

@majiayu000
Copy link

Summary

  • ServerResponse.end() now correctly sets finished=true when called without a valid socket/handle
  • This ensures writableEnded returns true after end() is called, per Node.js spec

Test plan

  • Added regression test in test/regression/issue/25632.test.ts
  • All existing HTTP tests pass

Fixes #25632

…ocket

ServerResponse.end() was returning early without setting finished=true
when there was no valid socket/handle, causing writableEnded to remain
false. Per Node.js spec, writableEnded should always be true after
end() is called.

Fixes oven-sh#25632

Signed-off-by: lif <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Walkthrough

ServerResponse.end() was changed to mark the response finished when no underlying handle exists (setting finished/writableEnded), and a regression test was added to verify writableEnded/finished becomes true across several end() scenarios.

Changes

Cohort / File(s) Change Summary
Implementation fix
src/js/node/_http_server.ts
Adjusted ServerResponse.prototype.end control flow to set this.finished = true when the internal socket/handle is absent; removed invoking the callback as a substitute for marking finished.
Regression tests
test/regression/issue/25632.test.ts
Added tests covering: end() with no socket, end() with callback but no socket, end() with string chunk but no socket, and end() in a real HTTP server — asserting writableEnded/finished are true after end().

Suggested reviewers

  • Jarred-Sumner

Pre-merge checks

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing writableEnded behavior when end() is called without a socket.
Description check ✅ Passed The description covers the key aspects: what was fixed (finished=true setting), how it was verified (regression test and existing tests pass), and the issue reference.
Linked Issues check ✅ Passed The PR successfully addresses issue #25632 by ensuring ServerResponse.end() sets finished=true (making writableEnded true) even without a socket, matching Node.js spec behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the writableEnded behavior issue: the ServerResponse.end() fix and the regression test for issue #25632.

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.

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2247c38 and b00ee11.

📒 Files selected for processing (2)
  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js}

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js}: Use .$call() and .$apply() instead of .call() and .apply() to prevent user tampering with function invocation
Use string literal require() statements only; dynamic requires are not permitted
Export modules using export default { ... } syntax; modules are NOT ES modules
Use JSC intrinsics (prefixed with $) such as $Array.from(), $isCallable(), and $newArrayWithSize() for performance-critical operations
Use private globals and methods with $ prefix (e.g., $Array, map.$set()) instead of public JavaScript globals
Use $debug() for debug logging and $assert() for assertions; both are stripped in release builds
Validate function arguments using validators from internal/validators and throw $ERR_* error codes for invalid arguments
Use process.platform and process.arch for platform detection; these values are inlined and dead-code eliminated at build time

Files:

  • src/js/node/_http_server.ts
src/js/{builtins,node,bun,thirdparty,internal}/**/*.ts

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

Builtin functions must include this parameter typing in TypeScript to enable direct method binding in C++

Files:

  • src/js/node/_http_server.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test with files that end in *.test.{ts,js,jsx,tsx,mjs,cjs}
Do not write flaky tests. Never wait for time to pass in tests; always wait for the condition to be met instead of using an arbitrary amount of time
Never use hardcoded port numbers in tests. Always use port: 0 to get a random port
Prefer concurrent tests over sequential tests using test.concurrent or describe.concurrent when multiple tests spawn processes or write files, unless it's very difficult to make them concurrent
When spawning Bun processes in tests, use bunExe and bunEnv from harness to ensure the same build of Bun is used and debug logging is silenced
Use -e flag for single-file tests when spawning Bun processes
Use tempDir() from harness to create temporary directories with files for multi-file tests instead of creating files manually
Prefer async/await over callbacks in tests
When callbacks must be used and it's just a single callback, use Promise.withResolvers to create a promise that can be resolved or rejected from a callback
Do not set a timeout on tests. Bun already has timeouts
Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests, as ''.repeat is very slow in debug JavaScriptCore builds
Use describe blocks for grouping related tests
Always use await using or using to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc
Always check exit codes and test error scenarios in error tests
Use describe.each() for parameterized tests
Use toMatchSnapshot() for snapshot testing
Use beforeAll(), afterEach(), beforeEach() for setup/teardown in tests
Track resources (servers, clients) in arrays for cleanup in afterEach()

Files:

  • test/regression/issue/25632.test.ts
test/regression/issue/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

Regression tests for specific issues go in /test/regression/issue/${issueNumber}.test.ts. Do not put tests without issue numbers in the regression directory

Files:

  • test/regression/issue/25632.test.ts
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/regression/issue/25632.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/25632.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/regression/issue/25632.test.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 22756
File: src/js/node/_http_server.ts:945-947
Timestamp: 2025-09-19T21:17:04.690Z
Learning: JSNodeHTTPServerSocket in src/bun.js/bindings/NodeHTTP.cpp implements both end() and close() methods. The end() method properly handles socket termination for both HTTP responses and raw CONNECT sockets by setting ended=true, checking buffered data, and flushing remaining data through the response context.
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-203
Timestamp: 2025-10-19T04:55:33.099Z
Learning: In test/js/bun/http/node-telemetry.test.ts and the Bun.telemetry._node_binding API, after the architecture refactor, the _node_binding interface only contains two methods: handleIncomingRequest(req, res) and handleWriteHead(res, statusCode). The handleRequestFinish hook and other lifecycle hooks were removed during simplification. Both current methods are fully tested.
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-88
Timestamp: 2025-10-19T04:55:27.213Z
Learning: In the Bun.telemetry._node_binding API for Node.js http.createServer compatibility, there are exactly two hooks: handleIncomingRequest(req, res) called once per request (returns a request ID), and handleWriteHead(res, statusCode) called once when headers are sent (explicit or implicit). The old handleRequestFinish hook no longer exists after architectural refactoring. Both hooks are tested in test/js/bun/http/node-telemetry.test.ts.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
📚 Learning: 2025-09-19T21:17:04.690Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 22756
File: src/js/node/_http_server.ts:945-947
Timestamp: 2025-09-19T21:17:04.690Z
Learning: JSNodeHTTPServerSocket in src/bun.js/bindings/NodeHTTP.cpp implements both end() and close() methods. The end() method properly handles socket termination for both HTTP responses and raw CONNECT sockets by setting ended=true, checking buffered data, and flushing remaining data through the response context.

Applied to files:

  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
📚 Learning: 2025-10-19T04:55:27.213Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-88
Timestamp: 2025-10-19T04:55:27.213Z
Learning: In the Bun.telemetry._node_binding API for Node.js http.createServer compatibility, there are exactly two hooks: handleIncomingRequest(req, res) called once per request (returns a request ID), and handleWriteHead(res, statusCode) called once when headers are sent (explicit or implicit). The old handleRequestFinish hook no longer exists after architectural refactoring. Both hooks are tested in test/js/bun/http/node-telemetry.test.ts.

Applied to files:

  • src/js/node/_http_server.ts
📚 Learning: 2025-10-19T04:55:33.099Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-203
Timestamp: 2025-10-19T04:55:33.099Z
Learning: In test/js/bun/http/node-telemetry.test.ts and the Bun.telemetry._node_binding API, after the architecture refactor, the _node_binding interface only contains two methods: handleIncomingRequest(req, res) and handleWriteHead(res, statusCode). The handleRequestFinish hook and other lifecycle hooks were removed during simplification. Both current methods are fully tested.

Applied to files:

  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/regression/issue/**/*.test.ts : Regression tests for specific issues go in `/test/regression/issue/${issueNumber}.test.ts`. Do not put tests without issue numbers in the regression directory

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-10-18T05:23:24.403Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/telemetry-server.test.ts:91-100
Timestamp: 2025-10-18T05:23:24.403Z
Learning: In the Bun codebase, telemetry tests (test/js/bun/telemetry-*.test.ts) should focus on telemetry API behavior: configure/disable/isEnabled, callback signatures and invocation, request ID correlation, and error handling. HTTP protocol behaviors like status code normalization (e.g., 200 with empty body → 204) should be tested in HTTP server tests (test/js/bun/http/), not in telemetry tests. Keep separation of concerns: telemetry tests verify the telemetry API contract; HTTP tests verify HTTP semantics.

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures and not tests themselves

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Track resources (servers, clients) in arrays for cleanup in `afterEach()`

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always check exit codes and test error scenarios in error tests

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always use `await using` or `using` to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc

Applied to files:

  • test/regression/issue/25632.test.ts
🧬 Code graph analysis (1)
test/regression/issue/25632.test.ts (1)
test/js/node/test/parallel/test-http-outgoing-proto.js (1)
  • ServerResponse (8-8)
🔇 Additional comments (4)
src/js/node/_http_server.ts (1)

1335-1336: LGTM! Correct fix for writableEnded behavior.

The fix correctly sets finished = true when end() is called without a valid handle, ensuring writableEnded returns true as per Node.js spec. This makes the behavior consistent with the code path at line 1377 where finished is also set to true when a handle exists.

test/regression/issue/25632.test.ts (3)

11-24: LGTM! Comprehensive test for the fix.

This test directly validates that writableEnded and finished become true after calling end() without a valid socket, which is the core issue being fixed.


26-41: LGTM! Validates callback behavior.

This test ensures that the callback handling via process.nextTick still works correctly after the fix, and that writableEnded becomes true immediately when end() is called.


43-51: LGTM! Covers edge case with data chunk.

This test validates that passing data to end() doesn't prevent writableEnded from becoming true when there's no socket.

Copy link
Collaborator

@Jarred-Sumner Jarred-Sumner left a comment

Choose a reason for hiding this comment

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

If we rewrite the test using node:test and try running it with node, the test should be true after end() is called with callback but without socket fails in node.

/**
 * Regression test for issue #25632
 * ServerResponse.end() should always result in writableEnded being set to/returning true
 *
 * @see https://github.com/oven-sh/bun/issues/25632
 */
import assert from "node:assert";
import http, { createServer, ServerResponse } from "node:http";
import { describe, test } from "node:test";

describe("ServerResponse.writableEnded", () => {
  test("should be true after end() is called without a socket", async () => {
    // Create a ServerResponse without a valid socket/handle
    const req = new http.IncomingMessage(null as any);
    const res = new ServerResponse(req);

    assert.strictEqual(res.writableEnded, false);
    assert.strictEqual(res.finished, false);

    res.end();

    // Per Node.js spec, writableEnded should be true after end() is called
    assert.strictEqual(res.writableEnded, true);
    assert.strictEqual(res.finished, true);
  });

  test("should be true after end() is called with callback but without socket", async () => {
    const req = new http.IncomingMessage(null as any);
    const res = new ServerResponse(req);

    let callbackCalled = false;
    res.end(() => {
      callbackCalled = true;
    });

    assert.strictEqual(res.writableEnded, true);
    assert.strictEqual(res.finished, true);

    // Wait for callback to be called via nextTick
    await new Promise(resolve => process.nextTick(resolve));
    assert.strictEqual(callbackCalled, true);
  });

  test("should be true after end() with chunk but without socket", async () => {
    const req = new http.IncomingMessage(null as any);
    const res = new ServerResponse(req);

    res.end("test data");

    assert.strictEqual(res.writableEnded, true);
    assert.strictEqual(res.finished, true);
  });

  test("should be true in normal server context", async () => {
    const server = createServer((req, res) => {
      assert.strictEqual(res.writableEnded, false);
      res.end("Hello");
      assert.strictEqual(res.writableEnded, true);
    });

    server.listen({ port: 0 });
    const { port } = server.address() as { port: number };

    try {
      const response = await fetch(`http://localhost:${port}`);
      assert.strictEqual(await response.text(), "Hello");
    } finally {
      server.close();
    }
  });
});

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b00ee11 and 36f48fd.

📒 Files selected for processing (2)
  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js}

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js}: Use .$call() and .$apply() instead of .call() and .apply() to prevent user tampering with function invocation
Use string literal require() statements only; dynamic requires are not permitted
Export modules using export default { ... } syntax; modules are NOT ES modules
Use JSC intrinsics (prefixed with $) such as $Array.from(), $isCallable(), and $newArrayWithSize() for performance-critical operations
Use private globals and methods with $ prefix (e.g., $Array, map.$set()) instead of public JavaScript globals
Use $debug() for debug logging and $assert() for assertions; both are stripped in release builds
Validate function arguments using validators from internal/validators and throw $ERR_* error codes for invalid arguments
Use process.platform and process.arch for platform detection; these values are inlined and dead-code eliminated at build time

Files:

  • src/js/node/_http_server.ts
src/js/{builtins,node,bun,thirdparty,internal}/**/*.ts

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

Builtin functions must include this parameter typing in TypeScript to enable direct method binding in C++

Files:

  • src/js/node/_http_server.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test with files that end in *.test.{ts,js,jsx,tsx,mjs,cjs}
Do not write flaky tests. Never wait for time to pass in tests; always wait for the condition to be met instead of using an arbitrary amount of time
Never use hardcoded port numbers in tests. Always use port: 0 to get a random port
Prefer concurrent tests over sequential tests using test.concurrent or describe.concurrent when multiple tests spawn processes or write files, unless it's very difficult to make them concurrent
When spawning Bun processes in tests, use bunExe and bunEnv from harness to ensure the same build of Bun is used and debug logging is silenced
Use -e flag for single-file tests when spawning Bun processes
Use tempDir() from harness to create temporary directories with files for multi-file tests instead of creating files manually
Prefer async/await over callbacks in tests
When callbacks must be used and it's just a single callback, use Promise.withResolvers to create a promise that can be resolved or rejected from a callback
Do not set a timeout on tests. Bun already has timeouts
Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests, as ''.repeat is very slow in debug JavaScriptCore builds
Use describe blocks for grouping related tests
Always use await using or using to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc
Always check exit codes and test error scenarios in error tests
Use describe.each() for parameterized tests
Use toMatchSnapshot() for snapshot testing
Use beforeAll(), afterEach(), beforeEach() for setup/teardown in tests
Track resources (servers, clients) in arrays for cleanup in afterEach()

Files:

  • test/regression/issue/25632.test.ts
test/regression/issue/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

Regression tests for specific issues go in /test/regression/issue/${issueNumber}.test.ts. Do not put tests without issue numbers in the regression directory

Files:

  • test/regression/issue/25632.test.ts
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/regression/issue/25632.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/25632.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/regression/issue/25632.test.ts
🧠 Learnings (19)
📓 Common learnings
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 22756
File: src/js/node/_http_server.ts:945-947
Timestamp: 2025-09-19T21:17:04.690Z
Learning: JSNodeHTTPServerSocket in src/bun.js/bindings/NodeHTTP.cpp implements both end() and close() methods. The end() method properly handles socket termination for both HTTP responses and raw CONNECT sockets by setting ended=true, checking buffered data, and flushing remaining data through the response context.
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-203
Timestamp: 2025-10-19T04:55:33.099Z
Learning: In test/js/bun/http/node-telemetry.test.ts and the Bun.telemetry._node_binding API, after the architecture refactor, the _node_binding interface only contains two methods: handleIncomingRequest(req, res) and handleWriteHead(res, statusCode). The handleRequestFinish hook and other lifecycle hooks were removed during simplification. Both current methods are fully tested.
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-88
Timestamp: 2025-10-19T04:55:27.213Z
Learning: In the Bun.telemetry._node_binding API for Node.js http.createServer compatibility, there are exactly two hooks: handleIncomingRequest(req, res) called once per request (returns a request ID), and handleWriteHead(res, statusCode) called once when headers are sent (explicit or implicit). The old handleRequestFinish hook no longer exists after architectural refactoring. Both hooks are tested in test/js/bun/http/node-telemetry.test.ts.
📚 Learning: 2025-09-19T21:17:04.690Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 22756
File: src/js/node/_http_server.ts:945-947
Timestamp: 2025-09-19T21:17:04.690Z
Learning: JSNodeHTTPServerSocket in src/bun.js/bindings/NodeHTTP.cpp implements both end() and close() methods. The end() method properly handles socket termination for both HTTP responses and raw CONNECT sockets by setting ended=true, checking buffered data, and flushing remaining data through the response context.

Applied to files:

  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
📚 Learning: 2025-10-19T04:55:33.099Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-203
Timestamp: 2025-10-19T04:55:33.099Z
Learning: In test/js/bun/http/node-telemetry.test.ts and the Bun.telemetry._node_binding API, after the architecture refactor, the _node_binding interface only contains two methods: handleIncomingRequest(req, res) and handleWriteHead(res, statusCode). The handleRequestFinish hook and other lifecycle hooks were removed during simplification. Both current methods are fully tested.

Applied to files:

  • src/js/node/_http_server.ts
  • test/regression/issue/25632.test.ts
📚 Learning: 2025-10-19T04:55:27.213Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/http/node-telemetry.test.ts:27-88
Timestamp: 2025-10-19T04:55:27.213Z
Learning: In the Bun.telemetry._node_binding API for Node.js http.createServer compatibility, there are exactly two hooks: handleIncomingRequest(req, res) called once per request (returns a request ID), and handleWriteHead(res, statusCode) called once when headers are sent (explicit or implicit). The old handleRequestFinish hook no longer exists after architectural refactoring. Both hooks are tested in test/js/bun/http/node-telemetry.test.ts.

Applied to files:

  • src/js/node/_http_server.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/regression/issue/**/*.test.ts : Regression tests for specific issues go in `/test/regression/issue/${issueNumber}.test.ts`. Do not put tests without issue numbers in the regression directory

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always check exit codes and test error scenarios in error tests

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Track resources (servers, clients) in arrays for cleanup in `afterEach()`

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-10-18T05:23:24.403Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/telemetry-server.test.ts:91-100
Timestamp: 2025-10-18T05:23:24.403Z
Learning: In the Bun codebase, telemetry tests (test/js/bun/telemetry-*.test.ts) should focus on telemetry API behavior: configure/disable/isEnabled, callback signatures and invocation, request ID correlation, and error handling. HTTP protocol behaviors like status code normalization (e.g., 200 with empty body → 204) should be tested in HTTP server tests (test/js/bun/http/), not in telemetry tests. Keep separation of concerns: telemetry tests verify the telemetry API contract; HTTP tests verify HTTP semantics.

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures and not tests themselves

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always use `await using` or `using` to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Never use hardcoded port numbers in tests. Always use `port: 0` to get a random port

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/**/*.test.ts?(x) : Always use `port: 0` in tests - do not hardcode ports or use custom random port number functions

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-09-03T01:30:58.001Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 21728
File: test/js/valkey/valkey.test.ts:264-271
Timestamp: 2025-09-03T01:30:58.001Z
Learning: For test/js/valkey/valkey.test.ts PUB/SUB tests, avoid arbitrary sleeps and async-forEach. Instead, resolve a Promise from the subscriber callback when the expected number of messages is observed and await it with a bounded timeout (e.g., withTimeout + Promise.withResolvers) to account for Redis server→subscriber propagation.

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-09-02T05:33:37.517Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22323
File: test/js/web/websocket/websocket-subprotocol.test.ts:74-75
Timestamp: 2025-09-02T05:33:37.517Z
Learning: In Bun's runtime, `await using` with Node.js APIs like `net.createServer()` is properly supported and should not be replaced with explicit cleanup. Bun has extended Node.js APIs with proper async dispose support.

Applied to files:

  • test/regression/issue/25632.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Prefer async/await over callbacks in tests

Applied to files:

  • test/regression/issue/25632.test.ts
🧬 Code graph analysis (1)
test/regression/issue/25632.test.ts (1)
test/js/node/test/parallel/test-http-outgoing-proto.js (1)
  • ServerResponse (8-8)
🔇 Additional comments (4)
test/regression/issue/25632.test.ts (3)

11-24: LGTM!

The test correctly verifies that writableEnded and finished return true after end() is called without a socket, which is the core fix for issue #25632.


40-48: LGTM!

The test correctly verifies that writableEnded and finished are true after end() is called with a data chunk but without a socket.


50-66: LGTM!

The test validates normal server operation and properly cleans up resources. The server cleanup is correctly awaited (line 64), which addresses the past review comment and ensures proper resource management as per coding guidelines.

src/js/node/_http_server.ts (1)

1334-1340: The no-handle behavior correctly matches Node.js.

The implementation properly handles the case where there is no socket:

  • Sets finished = true to make writableEnded return true
  • Does not invoke the callback (verified Node.js does the same)
  • Does not emit events (verified Node.js emits nothing: prefinish, finish, and close are not triggered)

The code and comments accurately document the intended behavior.

Comment on lines 26 to 38
test("should be true after end() is called with callback but without socket", async () => {
const req = new http.IncomingMessage(null as any);
const res = new ServerResponse(req);

res.end(() => {
// Note: In Node.js, callback is NOT invoked when there's no socket
// This matches Node.js behavior where the 'finish' event never fires without a socket
});

// Per Node.js spec, writableEnded should be true after end() is called
expect(res.writableEnded).toBe(true);
expect(res.finished).toBe(true);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider explicitly testing that callback is not invoked.

The test passes a callback to end() and documents that it should not be invoked (lines 31-32), but doesn't actually verify this behavior. While the test correctly checks writableEnded and finished, explicitly testing callback non-invocation would make the test more robust and self-documenting.

🔎 Optional enhancement to verify callback behavior
  test("should be true after end() is called with callback but without socket", async () => {
    const req = new http.IncomingMessage(null as any);
    const res = new ServerResponse(req);
+   let callbackInvoked = false;

    res.end(() => {
-     // Note: In Node.js, callback is NOT invoked when there's no socket
-     // This matches Node.js behavior where the 'finish' event never fires without a socket
+     callbackInvoked = true;
    });

    // Per Node.js spec, writableEnded should be true after end() is called
    expect(res.writableEnded).toBe(true);
    expect(res.finished).toBe(true);
+   
+   // Wait one tick to ensure callback would have been invoked if it were going to be
+   await new Promise(resolve => process.nextTick(resolve));
+   expect(callbackInvoked).toBe(false); // Callback should NOT be invoked without socket
  });
🤖 Prompt for AI Agents
In test/regression/issue/25632.test.ts around lines 26 to 38, the test documents
that the callback passed to res.end() should not be invoked but never asserts
it; add an explicit assertion by replacing the empty callback with a spy/flag
(e.g., a jest.fn() or a boolean set inside the callback) when calling
res.end(...) then assert that the spy/flag was not called/changed after end()
and after any necessary microtask ticks, keeping the existing expectations for
writableEnded and finished.

sosukesuzuki and others added 24 commits January 4, 2026 18:59
- WTFMove → WTF::move / std::move: Replaced WTFMove() macro with
WTF::move() function for WTF types, std::move() for std types
- SortedArrayMap removed: Replaced with if-else chains in
EventFactory.cpp, JSCryptoKeyUsage.cpp
- Wasm::Memory::create signature changed: Removed VM parameter
- URLPattern allocation: Changed from WTF_MAKE_ISO_ALLOCATED to
WTF_MAKE_TZONE_ALLOCATED

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…characters + fix (oven-sh#25697)

### What does this PR do?

- Add test that is broken before the changes in the code and fix
previous test making script in dependency takes a bit of time to be
executed. Without the `setTimeout` in the tests, due race conditions it
always success. I tried adding a test combining both tests, with
dependencies `dep0` and `larger-than-8-char`, but if the timeout is the
same it success.
- Fix for the use case added, by using the correct buffer for
`Dependency.name` otherwise it gets garbage when package name is larger
than 8 characters. This should fix oven-sh#12203

### How did you verify your code works?

Undo the changes in the code to verify the new test fails and check it
again after adding the changes in the code.
## Summary
- Remove sccache support entirely, use ccache only
- Missing ccache no longer fails the build (just skips caching)
- Remove S3 distributed cache support

## Changes
- Remove `cmake/tools/SetupSccache.cmake` and S3 distributed cache
support
- Simplify `CMakeLists.txt` to only use ccache
- Update `SetupCcache.cmake` to not fail when ccache is missing
- Replace sccache with ccache in bootstrap scripts (sh, ps1)
- Update `.buildkite/Dockerfile` to install ccache instead of sccache
- Update `flake.nix` and `shell.nix` to use ccache
- Update documentation (CONTRIBUTING.md, contributing.mdx,
building-windows.mdx)
- Remove `scripts/build-cache/` directory (was only for sccache S3
access)

## Test plan
- [x] Build completes successfully with `bun bd`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
…h#25710)

## Summary
- Fixes dead code elimination producing invalid syntax like `{ ...a, x:
}` when simplifying empty objects in spread contexts
- The issue was that `simplifyUnusedExpr` and `joinAllWithCommaCallback`
could return `E.Missing` instead of `null` to indicate "no side effects"
- Added checks to return `null` when the result is `E.Missing`

Fixes oven-sh#25609

## Test plan
- [x] Added regression test that fails on v1.3.5 and passes with fix
- [x] `bun bd test test/regression/issue/25609.test.ts` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <[email protected]>
note: This is due to constant folding by the JIT. For `String#includes`
on dynamic strings, the performance improvement is not this significant.

**before:**
```
$ bun ./bench/snippets/string-includes.mjs
cpu: Apple M4 Max
runtime: bun 1.3.5 (arm64-darwin)

benchmark                                  time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------------------- -----------------------------
String.includes - short, hit (middle)   82.24 ns/iter     (14.95 ns … 881 ns)  84.98 ns    470 ns    791 ns
String.includes - short, hit (start)    37.44 ns/iter      (8.46 ns … 774 ns)  26.08 ns    379 ns    598 ns
String.includes - short, hit (end)      97.27 ns/iter     (16.93 ns … 823 ns)    107 ns    537 ns    801 ns
String.includes - short, miss             102 ns/iter       (0 ps … 1'598 µs)     42 ns    125 ns    167 ns
String.includes - long, hit (middle)    16.01 ns/iter     (14.34 ns … 115 ns)  16.03 ns   20.1 ns   53.1 ns
String.includes - long, miss              945 ns/iter       (935 ns … 972 ns)    948 ns    960 ns    972 ns
String.includes - with position          9.83 ns/iter    (8.44 ns … 58.45 ns)   9.83 ns  12.31 ns  15.69 ns
```

**after:**
```
$ ./build/release/bun bench/snippets/string-includes.mjs
cpu: Apple M4 Max
runtime: bun 1.3.6 (arm64-darwin)

benchmark                                  time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------------------- -----------------------------
String.includes - short, hit (middle)     243 ps/iter     (203 ps … 10.13 ns)    244 ps    325 ps    509 ps !
String.includes - short, hit (start)      374 ps/iter     (244 ps … 19.78 ns)    387 ps    488 ps    691 ps
String.includes - short, hit (end)        708 ps/iter     (407 ps … 18.03 ns)    651 ps   2.62 ns   2.69 ns
String.includes - short, miss            1.49 ns/iter     (407 ps … 27.93 ns)   2.87 ns   3.09 ns   3.78 ns
String.includes - long, hit (middle)     3.28 ns/iter      (3.05 ns … 118 ns)   3.15 ns   8.75 ns  16.07 ns
String.includes - long, miss             7.28 ns/iter      (3.44 ns … 698 ns)   9.34 ns  42.85 ns    240 ns
String.includes - with position          7.97 ns/iter       (3.7 ns … 602 ns)   9.68 ns  52.19 ns    286 ns
```
…sh#25698)

## Summary

- Reject null bytes in command-line arguments passed to `Bun.spawn` and
`Bun.spawnSync`
- Reject null bytes in environment variable keys and values
- Reject null bytes in shell (`$`) template literal arguments

This prevents null byte injection attacks (CWE-158) where null bytes in
strings could cause unintended truncation when passed to the OS,
potentially allowing attackers to bypass file extension validation or
create files with unexpected names.

## Test plan

- [x] Added tests in `test/js/bun/spawn/null-byte-injection.test.ts`
- [x] Tests pass with debug build: `bun bd test
test/js/bun/spawn/null-byte-injection.test.ts`
- [x] Tests fail with system Bun (confirming the fix works)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Jarred Sumner <[email protected]>
## What does this PR do?
Switch `Bun.hash.crc32` to use `zlib`'s CRC32 implementation. Bun
already links `zlib`, which provides highly optimized,
hardware-accelerated CRC32. Because `zlib.crc32` takes a 32-bit length,
chunk large inputs to avoid truncation/regressions on buffers >4GB.

Note: This was tried before (PR oven-sh#12164 by Jarred), which switched CRC32
to zlib for speed. This proposal keeps that approach and adds explicit
chunking to avoid the >4GB length pitfall.

**Problem:** `Bun.hash.crc32` is a significant outlier in
microbenchmarks compared to other hash functions (about 21x slower than
`zlib.crc32` in a 1MB test on M1).

**Root cause:** `Bun.hash.crc32` uses Zig's `std.hash.Crc32`
implementation, which is software-only and does not leverage hardware
acceleration (e.g., `PCLMULQDQ` on x86 or `CRC32` instructions on ARM).

**Implementation:**
https://github.com/oven-sh/bun/blob/main/src/bun.js/api/HashObject.zig

```zig
pub const crc32 = hashWrap(struct {
    pub fn hash(seed: u32, bytes: []const u8) u32 {
        // zlib takes a 32-bit length, so chunk large inputs to avoid truncation.
        var crc: u64 = seed;
        var offset: usize = 0;
        while (offset < bytes.len) {
            const remaining = bytes.len - offset;
            const max_len: usize = std.math.maxInt(u32);
            const chunk_len: u32 = if (remaining > max_len) @intcast(max_len) else @intcast(remaining);
            crc = bun.zlib.crc32(crc, bytes.ptr + offset, chunk_len);
            offset += chunk_len;
        }
        return @intcast(crc);
    }
});
```

### How did you verify your code works?
**Benchmark (1MB payload):**
- **Before:** Bun 1.3.5 `Bun.hash.crc32` = 2,644,444 ns/op vs
`zlib.crc32` = 124,324 ns/op (~21x slower)
- **After (local bun-debug):** `Bun.hash.crc32` = 360,591 ns/op vs
`zlib.crc32` = 359,069 ns/op (~1.0x), results match

## Test environment
- **Machine:** MacBook Pro 13" (M1, 2020)
- **OS:** macOS 15.7.3
- **Baseline Bun:** 1.3.5
- **After Bun:** local `bun-debug` (build/debug)
**before:**

```
$ bun bench/snippets/array-of.js
cpu: Apple M4 Max
runtime: bun 1.3.5 (arm64-darwin)

benchmark                                  time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------------------- -----------------------------
int: Array.of(1,2,3,4,5)                 9.19 ns/iter       (8.1 ns … 108 ns)   9.28 ns  13.63 ns  69.44 ns
int: Array.of(100 elements)             1'055 ns/iter   (94.58 ns … 1'216 ns)  1'108 ns  1'209 ns  1'216 ns
double: Array.of(1.1,2.2,3.3,4.4,5.5)   10.34 ns/iter      (8.81 ns … 102 ns)  10.17 ns  17.19 ns  73.51 ns
double: Array.of(100 elements)          1'073 ns/iter     (124 ns … 1'215 ns)  1'136 ns  1'204 ns  1'215 ns
object: Array.of(obj x5)                19.19 ns/iter     (16.58 ns … 122 ns)  19.06 ns   77.6 ns  85.75 ns
object: Array.of(100 elements)          1'340 ns/iter     (294 ns … 1'568 ns)  1'465 ns  1'537 ns  1'568 ns
```

**after:**

```
$ ./build/release/bun bench/snippets/array-of.js
cpu: Apple M4 Max
runtime: bun 1.3.6 (arm64-darwin)

benchmark                                  time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------------------- -----------------------------
int: Array.of(1,2,3,4,5)                 2.68 ns/iter    (2.14 ns … 92.96 ns)   2.52 ns   3.95 ns  59.73 ns
int: Array.of(100 elements)             23.69 ns/iter     (18.88 ns … 155 ns)  20.91 ns  83.82 ns  96.66 ns
double: Array.of(1.1,2.2,3.3,4.4,5.5)    3.62 ns/iter    (2.97 ns … 75.44 ns)   3.46 ns   5.05 ns  65.82 ns
double: Array.of(100 elements)          26.96 ns/iter     (20.14 ns … 156 ns)  24.45 ns  87.75 ns  96.88 ns
object: Array.of(obj x5)                11.82 ns/iter     (9.6 ns … 87.38 ns)  11.23 ns  68.99 ns  77.09 ns
object: Array.of(100 elements)            236 ns/iter       (206 ns … 420 ns)    273 ns    325 ns    386 ns
```
…or space (oven-sh#25717)

## Summary

- Fix performance regression where `Response.json()` was 2-3x slower
than `JSON.stringify() + new Response()`
- Root cause: The existing code called `JSC::JSONStringify` with
`indent=0`, which internally passes `jsNumber(0)` as the space
parameter. This bypasses WebKit's FastStringifier optimization.
- Fix: Add a new `jsonStringifyFast` binding that passes `jsUndefined()`
for the space parameter, triggering JSC's FastStringifier
(SIMD-optimized) code path.

## Root Cause Analysis

In WebKit's `JSONObject.cpp`, the `stringify()` function has this logic:

```cpp
static NEVER_INLINE String stringify(JSGlobalObject& globalObject, JSValue value, JSValue replacer, JSValue space)
{
    // ...
    if (String result = FastStringifier<Latin1Character, BufferMode::StaticBuffer>::stringify(globalObject, value, replacer, space, failureReason); !result.isNull())
        return result;
    // Falls back to slow Stringifier...
}
```

And `FastStringifier::stringify()` checks:
```cpp
if (!space.isUndefined()) {
    logOutcome("space"_s);
    return { };  // Bail out to slow path
}
```

So when we called `JSONStringify(globalObject, value, (unsigned)0)`, it
converted to `jsNumber(0)` which is NOT `undefined`, causing
FastStringifier to bail out.

## Performance Results

### Before (3.5x slower than manual approach)
```
Response.json():                2415ms
JSON.stringify() + Response():  689ms
Ratio:                          3.50x
```

### After (parity with manual approach)
```
Response.json():                ~700ms  
JSON.stringify() + Response():  ~700ms
Ratio:                          ~1.09x
```

## Test plan

- [x] Existing `Response.json()` tests pass
(`test/regression/issue/21257.test.ts`)
- [x] Response tests pass (`test/js/web/fetch/response.test.ts`)
- [x] Manual verification that output is correct for various JSON inputs

Fixes oven-sh#25693

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sosuke Suzuki <[email protected]>
### What does this PR do?

Enables `CHECK_REF_COUNTED_LIFECYCLE` in WebKit (
oven-sh/WebKit#121 )

See also
WebKit/WebKit@a978fae

#### `CHECK_REF_COUNTED_LIFECYCLE`?

A compile-time macro that enables lifecycle validation for
reference-counted objects in debug builds.

**Definition**
```cpp
  #if ASSERT_ENABLED || ENABLE(SECURITY_ASSERTIONS)
  #define CHECK_REF_COUNTED_LIFECYCLE 1
  #else
  #define CHECK_REF_COUNTED_LIFECYCLE 0
  #endif
```
**Purpose**

  Detects three categories of bugs:
1. Missing adoption - Objects stored in RefPtr without using adoptRef()
2. Ref during destruction - ref() called while destructor is running
(causes dangling pointers)
  3. Thread safety violations - Unsafe ref/deref across threads

**Implementation**

  When enabled, RefCountDebugger adds two tracking flags:
  - m_deletionHasBegun - Set when destructor starts
  - m_adoptionIsRequired - Cleared when adoptRef() is called

These flags are checked on every ref()/deref() call, with assertions
failing on violations.

**Motivation**

  Refactored debug code into a separate RefCountDebugger class to:
  - Improve readability of core refcount logic
- Eliminate duplicate code across RefCounted, ThreadSafeRefCounted, etc.
  - Simplify adding new refcount classes

 **Overhead**

Zero in release builds - the flags and checks are compiled out entirely.

### How did you verify your code works?

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
…sh#25720)

Closes oven-sh#8254

Fixes a data corruption bug in `Bun.write()` where files larger than 2GB
would have chunks skipped resulting in corrupted output with missing
data.

The `doWriteLoop` had an issue where it would essentially end up
offsetting twice every 2GB chunks:
-  it first sliced the buffer by `total_written`: 
```remain = remain[@min(this.total_written, remain.len)..]``` 
-  it would then increment `bytes_blob.offset`: 
`this.bytes_blob.offset += @truncate(wrote)` 

but because `sharedView()` already uses the blob offset `slice_ = slice_[this.offset..]` it would end up doubling the offset.

In a local reproduction writing a 16GB file with each 2GB chunk filled with incrementing values `[1, 2, 3, 4, 5, 6, 7, 8]`, the buggy version produced: `[1, 3, 5, 7, …]`, skipping every other chunk.

The fix is to simply remove the redundant manual offset and rely only on `total_written` to track write progress.
…on (oven-sh#25667)

## Summary

Adds `Bun.$.trace` for tracing shell commands without executing them.

```js
const result = $.trace`cat /tmp/file.txt > output.txt`;
// { operations: [...], cwd: "...", success: true, error: null }
```

## Test plan

- [x] `bun bd test test/js/bun/shell/trace.test.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <[email protected]>
## Summary
- Add maximum decompressed message size limit to WebSocket client
deflate handling
- Add test coverage for decompression limits

## Test plan
- Run `bun test
test/js/web/websocket/websocket-permessage-deflate-edge-cases.test.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
)

## Summary

Apply the same optimization technique from PR oven-sh#25717 (Response.json) to
other APIs that use JSON.stringify internally:

- **IPC message serialization** (`ipc.zig`) - used for inter-process
communication
- **console.log with %j format** (`ConsoleObject.zig`) - commonly used
for debugging
- **PostgreSQL JSON/JSONB types** (`PostgresRequest.zig`) - database
operations
- **MySQL JSON type** (`MySQLTypes.zig`) - database operations
- **Jest %j/%o format specifiers** (`jest.zig`) - test output formatting
- **Transpiler tsconfig/macros** (`JSTranspiler.zig`) - build
configuration

### Root Cause

When calling `JSONStringify(globalObject, value, 0)`, the space
parameter `0` becomes `jsNumber(0)`, which is NOT `undefined`. This
causes JSC's FastStringifier (SIMD-optimized) to bail out:

```cpp
// In WebKit's JSONObject.cpp FastStringifier::stringify()
if (!space.isUndefined()) {
    logOutcome("space"_s);
    return { };  // Bail out to slow path
}
```

Using `jsonStringifyFast` which passes `jsUndefined()` triggers the fast
path.

### Expected Performance Improvement

Based on PR oven-sh#25717 results, these changes should provide ~3x speedup for
JSON serialization in the affected APIs.

## Test plan

- [x] Debug build compiles successfully
- [x] Basic functionality verified (IPC, console.log %j, Response.json)
- [x] Existing tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
…sh#25731)

Fixes oven-sh#25716

Adds support for a `reactFastRefresh: boolean` option in the `Bun.build`
JavaScript API, matching the existing `--react-fast-refresh` CLI flag.

```ts
const result = await Bun.build({
    reactFastRefresh: true,
    entrypoints: ["src/App.tsx"],
});
```

When enabled, the bundler adds React Fast Refresh transform code
(`$RefreshReg$`, `$RefreshSig$`) to the output.
)

## Summary

Optimize `Buffer.indexOf` and `Buffer.includes` by replacing
`std::search` with Highway SIMD-optimized functions:

- **Single-byte search**: `highway_index_of_char` - SIMD-accelerated
character search
- **Multi-byte search**: `highway_memmem` - SIMD-accelerated substring
search

These Highway functions are already used throughout Bun's codebase for
fast string searching (URL parsing, JS lexer, etc.) and provide
significant speedups for pattern matching in large buffers.

### Changes

```cpp
// Before: std::search (scalar)
auto it = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end());

// After: SIMD-optimized
if (valueLength == 1) {
    size_t result = highway_index_of_char(haystackPtr, haystackLen, valuePtr[0]);
} else {
    void* result = highway_memmem(haystackPtr, haystackLen, valuePtr, valueLength);
}
```

## Test plan

- [x] Debug build compiles successfully
- [x] Basic functionality verified (`indexOf`, `includes` with single
and multi-byte patterns)
- [ ] Existing Buffer tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
…h#25743)

## Summary

- Fix O(n²) performance bug in JSON mode IPC when receiving large
messages that arrive in chunks
- Add `JsonIncomingBuffer` wrapper that tracks newline positions to
avoid re-scanning
- Each byte is now scanned exactly once (on arrival or when preceding
message is consumed)

## Problem

When data arrives in chunks in JSON mode, `decodeIPCMessage` was calling
`indexOfChar(data, '\n')` on the ENTIRE accumulated buffer every time.
For a 10MB message arriving in 160 chunks of 64KB:

- Chunk 1: scan 64KB
- Chunk 2: scan 128KB  
- Chunk 3: scan 192KB
- ...
- Chunk 160: scan 10MB

Total: ~800MB scanned for one 10MB message.

## Solution

Introduced a `JsonIncomingBuffer` struct that:
1. Tracks `newline_pos: ?u32` - position of known upcoming newline (if
any)
2. On `append(bytes)`: Only scans new chunk for `\n` if no position is
cached
3. On `consume(bytes)`: Updates or re-scans as needed after message
processing

This ensures O(n) scanning instead of O(n²).

## Test plan

- [x] `bun run zig:check-all` passes (all platforms compile)
- [x] `bun bd test test/js/bun/spawn/spawn.ipc.test.ts` - 4 tests pass
- [x] `bun bd test test/js/node/child_process/child_process_ipc.test.js`
- 1 test pass
- [x] `bun bd test test/js/bun/spawn/bun-ipc-inherit.test.ts` - 1 test
pass
- [x] `bun bd test test/js/bun/spawn/spawn.ipc.bun-node.test.ts` - 1
test pass
- [x] `bun bd test test/js/bun/spawn/spawn.ipc.node-bun.test.ts` - 1
test pass
- [x] `bun bd test
test/js/node/child_process/child_process_ipc_large_disconnect.test.js` -
1 test pass
- [x] Manual verification with `child-process-send-cb-more.js` (32KB
messages)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <[email protected]>
## Summary

- Fix CMake JSON parsing error when Buildkite API returns commit
messages with newlines

CMake's `file(READ ...)` reads files with literal newlines, which breaks
`string(JSON ...)` when the JSON contains escape sequences like `\n` in
string values (e.g., commit messages from Buildkite API).

Use `file(STRINGS ...)` to read line-by-line, then join with `\n` to
preserve valid JSON escape sequences while avoiding literal newlines.

## Test plan

- Verify CMake configure succeeds when Buildkite build has commit
messages with newlines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <[email protected]>
Co-authored-by: Claude <[email protected]>
@majiayu000 majiayu000 requested a review from alii as a code owner January 4, 2026 11:03
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.

ServerResponse.end() should always result in writableEnded being set to/returning true

10 participants