Conversation
Adds a configurable maximum HTTP header count limit, similar to --max-http-header-size. This allows servers to accept requests with more than the default 100 headers. Features: - CLI flag: --max-http-header-count <INT> (default: 100) - Node.js API: http.maxHeadersCount getter/setter - Dynamic header allocation when limit > 100 (fast path for default) - Returns HTTP 431 when header count exceeds limit Fixes #6982 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughThis PR makes the HTTP header count limit configurable throughout Bun's stack. It replaces a hard-coded limit with a default of 100 and adds APIs at multiple layers (C++, C, Zig, TypeScript, CLI) to allow customization per application or globally via command-line flags. Changes
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Updated 5:26 PM PT - Jan 14th, 2026
❌ Your commit
🧪 To try this PR locally: bunx bun-pr 26122That installs a local version of the PR into your bun-26122 --bun |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@packages/bun-uws/src/HttpParser.h`:
- Line 757: The loop for parsing headers (for (unsigned int i = 1; i <
maxHeadersCount - 1; i++)), combined with headers[0] being used for the request
line, means the effective capacity is maxHeadersCount - 2; update either the
parsing logic or the CLI description: either change the loop bound to i <
maxHeadersCount (and adjust any sentinel/terminator handling and the comment
"This cann take the very last header space") so that setting
--max-http-header-count = N allows N headers, or keep the loop but change the
user-facing --max-http-header-count help text to explicitly state the value
reserves two slots (request line + terminator) and thus supports N-2 actual
headers; locate and modify the loop in HttpParser.h, the headers[0] usage, and
the CLI flag description accordingly.
In `@src/bun.js/node/node_http_binding.zig`:
- Around line 49-65: The setMaxHTTPHeadersCount function can panic when num
exceeds u32 max; add an upper-bound check after coercing num to ensure num <=
std.math.maxInt(u32) and return globalThis.throwInvalidArgumentTypeValue (or a
similar JS error) if it’s larger, otherwise safely assign
bun.http.max_http_headers_count using `@intCast`; reference the symbols
setMaxHTTPHeadersCount, num, std.math.maxInt(u32), `@intCast`, and
bun.http.max_http_headers_count when implementing the validation and error
return.
In `@test/regression/issue/6982.test.ts`:
- Around line 81-85: The current logic reads a single chunk from
proc.stdout.getReader() and decodes value into url, which can break if the URL
is split across chunks; change to a loop that repeatedly reads from
reader.read(), appends decoded chunks into a buffer, and stops when a newline is
encountered (or EOF), then trim to produce url; apply the same fix to the second
occurrence that also reads stdout (lines referenced around the second block).
📜 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.
📒 Files selected for processing (12)
packages/bun-uws/src/App.hpackages/bun-uws/src/HttpContext.hpackages/bun-uws/src/HttpContextData.hpackages/bun-uws/src/HttpParser.hsrc/bun.js/node/node_http_binding.zigsrc/cli/Arguments.zigsrc/deps/libuwsockets.cppsrc/deps/uws/App.zigsrc/http.zigsrc/js/internal/http.tssrc/js/node/http.tstest/regression/issue/6982.test.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/bun.js/node/node_http_binding.zigsrc/deps/uws/App.zigsrc/http.zigsrc/cli/Arguments.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct {#foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/bun.js/node/node_http_binding.zigsrc/deps/uws/App.zigsrc/http.zigsrc/cli/Arguments.zig
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 literalrequire()statements only; dynamic requires are not permitted
Export modules usingexport 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 frominternal/validatorsand throw$ERR_*error codes for invalid arguments
Useprocess.platformandprocess.archfor platform detection; these values are inlined and dead-code eliminated at build time
Files:
src/js/node/http.tssrc/js/internal/http.ts
src/js/{builtins,node,bun,thirdparty,internal}/**/*.ts
📄 CodeRabbit inference engine (src/js/CLAUDE.md)
Builtin functions must include
thisparameter typing in TypeScript to enable direct method binding in C++
Files:
src/js/node/http.tssrc/js/internal/http.ts
**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.ts?(x): Never usebun testdirectly - always usebun bd testto run tests with debug build changes
For single-file tests, prefer-eflag overtempDir
For multi-file tests, prefertempDirandBun.spawnover single-file tests
UsenormalizeBunSnapshotto normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
UsetempDirfromharnessto create temporary directories - do not usetmpdirSyncorfs.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 usesetTimeoutin tests; instead await the condition to be met
Verify tests fail withUSE_SYSTEM_BUN=1 bun test <file>and pass withbun bd test <file>- tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with.test.tsor.test.tsx
Avoid shell commands likefindorgrepin tests - use Bun's Glob and built-in tools instead
Files:
test/regression/issue/6982.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.tswith real issue numbers only
Files:
test/regression/issue/6982.test.ts
test/**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
Always use
port: 0in tests - do not hardcode ports or use custom random port number functions
Files:
test/regression/issue/6982.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}
📄 CodeRabbit inference engine (test/CLAUDE.md)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Usebun bd test <...test file>to run tests with compiled code changes. Do not usebun testas it will not include your changes.
Usebun:testfor files ending in*.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, usebun bd <file>instead ofbun bd test <file>since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.
Files:
test/regression/issue/6982.test.ts
🧠 Learnings (35)
📓 Common learnings
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24086
File: src/bun.js/api/server.zig:2534-2535
Timestamp: 2025-10-26T04:50:17.892Z
Learning: In src/bun.js/api/server.zig, u32 is acceptable for route indices and websocket_context_index fields, as the practical limit of 4 billion contexts will never be reached.
📚 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/bun.js/node/node_http_binding.zigtest/regression/issue/6982.test.tssrc/js/internal/http.ts
📚 Learning: 2025-10-17T20:50:58.644Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/Handlers.zig:154-159
Timestamp: 2025-10-17T20:50:58.644Z
Learning: In Bun socket configuration error messages (src/bun.js/api/bun/socket/Handlers.zig), use the user-facing JavaScript names "data" and "drain" instead of internal field names "onData" and "onWritable", as these are the names users see in the API according to SocketConfig.bindv2.ts.
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 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/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/node/node_http_binding.zigsrc/deps/uws/App.zig
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 Learning: 2025-10-26T04:50:17.892Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24086
File: src/bun.js/api/server.zig:2534-2535
Timestamp: 2025-10-26T04:50:17.892Z
Learning: In src/bun.js/api/server.zig, u32 is acceptable for route indices and websocket_context_index fields, as the practical limit of 4 billion contexts will never be reached.
Applied to files:
src/bun.js/node/node_http_binding.zigsrc/deps/uws/App.zigsrc/http.zigpackages/bun-uws/src/HttpParser.hsrc/js/internal/http.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/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 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 src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JavaScript class bindings
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 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-module/main.cpp : Register new V8 API test functions in the Init method using NODE_SET_METHOD with exports object
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 Learning: 2025-10-18T20:50:47.750Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:366-373
Timestamp: 2025-10-18T20:50:47.750Z
Learning: In Bun's Zig codebase (src/bun.js/bindings/JSValue.zig), the JSValue enum uses `.null` (not `.js_null`) for JavaScript's null value. Only `js_undefined` has the `js_` prefix to avoid collision with Zig's built-in `undefined` keyword. The correct enum fields are: `js_undefined`, `null`, `true`, `false`, and `zero`.
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 Learning: 2025-09-05T18:44:43.223Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 21728
File: src/bun.js/bindings/bindings.cpp:6429-6432
Timestamp: 2025-09-05T18:44:43.223Z
Learning: The JSC::JSMap::size() method in JavaScriptCore returns a value that can be represented as uint32_t, and the binding functions in src/bun.js/bindings/bindings.cpp correctly use uint32_t as the return type for JSC__JSMap__size. JavaScript Maps are practically limited to 2^32 - 1 elements.
Applied to files:
src/bun.js/node/node_http_binding.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/bun.js/node/node_http_binding.zigsrc/deps/uws/App.zigsrc/http.zigsrc/cli/Arguments.zig
📚 Learning: 2026-01-10T00:28:26.694Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 25938
File: packages/bun-uws/src/HttpParser.h:0-0
Timestamp: 2026-01-10T00:28:26.694Z
Learning: In packages/bun-uws/src/HttpParser.h, the headData and headLength fields on HttpRequest are intentionally left populated for all request types (not just CONNECT/upgrade), as they will be used in the future for parse errors and other Node.js compatibility features. They should not be cleared after the requestHandler call.
Applied to files:
packages/bun-uws/src/HttpContextData.hpackages/bun-uws/src/HttpContext.hpackages/bun-uws/src/HttpParser.h
📚 Learning: 2025-11-12T04:11:52.293Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 24622
File: src/deps/uws/us_socket_t.zig:112-113
Timestamp: 2025-11-12T04:11:52.293Z
Learning: In Bun's Zig codebase, when passing u32 values to C FFI functions that expect c_uint parameters, no explicit intCast is needed because c_uint is equivalent to u32 on Bun's target platforms and Zig allows implicit coercion between equivalent types. This pattern is used consistently throughout src/deps/uws/us_socket_t.zig in functions like setTimeout, setLongTimeout, and setKeepalive.
Applied to files:
src/deps/uws/App.zigpackages/bun-uws/src/HttpParser.hsrc/js/internal/http.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.ts
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/http.zigsrc/cli/Arguments.zig
📚 Learning: 2025-09-03T05:09:24.272Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22335
File: src/http.zig:1376-1393
Timestamp: 2025-09-03T05:09:24.272Z
Learning: In bun's HTTP module, `bun.http.default_allocator` is the correct allocator to use for internal HTTP buffers like MutableString initialization for response_message_buffer and compressed_body. This allocator is initialized in HTTPThread.zig as `bun.http.default_arena.allocator()` and is used consistently across the HTTP module, separate from any local `default_allocator` variables.
Applied to files:
src/http.zig
📚 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/6982.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/6982.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/6982.test.ts
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Applied to files:
test/regression/issue/6982.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) : 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
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Organize regression tests for specific issues in `/test/regression/issue/${issueNumber}.test.ts`. Do not place regression tests in the regression directory if there is no associated issue number.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-14T21:08:10.406Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/js/node/test/parallel/CLAUDE.md:0-0
Timestamp: 2026-01-14T21:08:10.406Z
Learning: These are Node.js compatibility tests not written by Bun and cannot be modified
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Do not set a timeout on tests. Bun already has timeouts built-in.
Applied to files:
test/regression/issue/6982.test.ts
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
packages/bun-uws/src/HttpParser.h
📚 Learning: 2025-10-15T20:19:38.580Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:38.580Z
Learning: In the Bun codebase, FFI is compiled with tcc (TinyCC), which barely supports C99. The headers `src/bun.js/api/FFI.h` and `src/bun.js/api/ffi-stdbool.h` are only used for FFI compilation with tcc, not for the main Bun target. Therefore, C23 compatibility concerns (such as bool/true/false keyword conflicts) do not apply to these FFI headers.
Applied to files:
packages/bun-uws/src/HttpParser.h
📚 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/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
packages/bun-uws/src/HttpParser.h
📚 Learning: 2025-10-01T21:48:38.278Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/Bindgen/IDLTypes.h:1-3
Timestamp: 2025-10-01T21:48:38.278Z
Learning: In the Bun codebase, for `BunIDL*` and `Bindgen*` headers (e.g., BunIDLTypes.h, Bindgen/IDLTypes.h), it's acceptable to rely on transitive includes for standard library headers like <type_traits> and <utility> rather than including them explicitly.
Applied to files:
packages/bun-uws/src/HttpParser.h
🧬 Code graph analysis (5)
packages/bun-uws/src/HttpContextData.h (1)
src/js/node/http.ts (2)
maxHeadersCount(64-66)maxHeadersCount(67-69)
src/js/node/http.ts (1)
src/js/internal/http.ts (2)
getMaxHTTPHeadersCount(507-507)setMaxHTTPHeadersCount(557-557)
test/regression/issue/6982.test.ts (1)
test/harness.ts (1)
tempDir(277-284)
packages/bun-uws/src/HttpParser.h (2)
src/js/node/http.ts (4)
maxHeadersCount(64-66)maxHeadersCount(67-69)maxHeaderSize(58-60)maxHeaderSize(61-63)packages/bun-uws/src/HttpContextData.h (1)
- `` (40-85)
packages/bun-uws/src/App.h (2)
src/js/internal/http.ts (1)
setMaxHTTPHeadersCount(557-557)src/js/node/http.ts (2)
maxHeadersCount(64-66)maxHeadersCount(67-69)
🔇 Additional comments (23)
src/deps/libuwsockets.cpp (1)
534-542: LGTM!The implementation correctly follows the established pattern used by
uws_app_set_max_http_header_sizeand other similar functions in this file. The SSL/non-SSL dispatch logic is consistent with the rest of the codebase.packages/bun-uws/src/HttpContextData.h (1)
73-73: LGTM!The new
maxHeadersCountfield is appropriately typed asuint32_t, has a sensible default value with clear documentation of the sentinel semantics (0 = use default), and follows the same pattern as the existingmaxHeaderSizefield.src/deps/uws/App.zig (1)
66-68: LGTM!The new
setMaxHTTPHeadersCountmethod correctly follows the established pattern used bysetMaxHTTPHeaderSize. The extern declaration at line 411 properly matches the C++ function signature.packages/bun-uws/src/App.h (1)
669-672: LGTM!The
setMaxHTTPHeadersCountmethod correctly mirrors the pattern established bysetMaxHTTPHeaderSize, maintaining API consistency and supporting the fluent builder pattern viastd::move(*this).src/http.zig (1)
18-21: LGTM!The global configuration variable is correctly defined with an appropriate default value of 100 and follows the established pattern used by
max_http_header_size. The export withBUN_DEFAULT_MAX_HTTP_HEADERS_COUNTenables access from other parts of the codebase.packages/bun-uws/src/HttpContext.h (1)
246-246: LGTM!The
maxHeadersCountparameter is correctly passed toconsumePostPadded, following the same pattern asmaxHeaderSize. The placement and ordering are consistent with the function signature.src/cli/Arguments.zig (2)
106-106: LGTM!The parameter declaration follows the established pattern used by
--max-http-header-sizeand provides a clear description.
739-749: LGTM!The parsing logic correctly mirrors the existing
--max-http-header-sizepattern. Usingu32is appropriate and matches the underlying C++ type inHttpContextData. The "0 means unlimited" behavior provides a consistent user experience.src/js/node/http.ts (2)
9-16: LGTM!The imports are correctly structured alongside the existing
setMaxHTTPHeaderSizeandgetMaxHTTPHeaderSizebindings.
64-69: LGTM!The getter/setter pair for
maxHeadersCountfollows the exact same pattern asmaxHeaderSize, maintaining API consistency.src/js/internal/http.ts (2)
355-356: LGTM!The Zig function bindings are correctly defined with appropriate arities (1 for setter, 0 for getter), following the established pattern for
setMaxHTTPHeaderSize/getMaxHTTPHeaderSize.
507-507: LGTM!The exports are correctly added and maintain alphabetical ordering within the export block.
Also applies to: 557-557
test/regression/issue/6982.test.ts (5)
1-17: LGTM!The test correctly validates the default getter value. Good use of
await usingfor process cleanup and proper sequencing of stdout before exitCode.
19-42: LGTM!The setter test properly validates both the initial default value and the updated value after assignment.
44-56: LGTM!The CLI flag test correctly verifies that
--max-http-header-count=200sets the value accessible viahttp.maxHeadersCount.
58-100: LGTM!The test correctly validates that a server with an increased header limit accepts requests with 150 headers. Good use of
port: 0and proper cleanup withproc.kill().
102-143: LGTM!The test correctly validates that the server returns HTTP 431 when header count exceeds the configured limit. This verifies the core functionality requested in issue
#6982.src/bun.js/node/node_http_binding.zig (1)
43-47: LGTM!The getter follows the established pattern from
getMaxHTTPHeaderSizeand correctly returns the current value.packages/bun-uws/src/HttpParser.h (5)
48-48: LGTM!The extern declaration follows the established pattern and uses appropriate type.
152-167: Good design with stack/heap hybrid allocation.The implementation provides a fast path with stack-allocated storage for the default case while supporting larger limits via dynamic allocation. The RAII-managed vector handles cleanup correctly.
176-192: LGTM!The dynamic allocation logic correctly switches between stack and heap storage based on the configured limit. The fallback to
BUN_DEFAULT_MAX_HTTP_HEADERS_COUNTwhen count is 0 ensures consistent default behavior.
859-860: LGTM!The per-request header limit is correctly threaded through to the
getHeaderscall.
992-997: LGTM!The public API correctly accepts the
maxHeadersCountparameter and configures theHttpRequestbefore parsing begins, enabling the dynamic allocation path when the limit exceeds the default.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| headers++; | ||
|
|
||
| for (unsigned int i = 1; i < UWS_HTTP_MAX_HEADERS_COUNT - 1; i++) { | ||
| for (unsigned int i = 1; i < maxHeadersCount - 1; i++) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "max-http-header-count" --type=ts --type=md -C2Repository: oven-sh/bun
Length of output: 1748
🏁 Script executed:
# Also search for related config/flag documentation
rg -n "maxHeadersCount\|max.*header.*count" --type=ts --type=md --type=js -C2 | head -50Repository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Examine the HttpParser.h file to understand the context around line 757
sed -n '700,850p' packages/bun-uws/src/HttpParser.h | cat -nRepository: oven-sh/bun
Length of output: 10011
🏁 Script executed:
cat test/regression/issue/6982.test.tsRepository: oven-sh/bun
Length of output: 4296
🏁 Script executed:
# Search for CLI flag documentation or help text
rg -n "max.*header.*count" packages/bun-uws/ -C3 --type=h --type=cppRepository: oven-sh/bun
Length of output: 1887
🏁 Script executed:
# Look for where maxHeadersCount is set/initialized
rg -n "maxHeadersCount\s*=" packages/bun-uws/src/ --type=h --type=cpp -C2Repository: oven-sh/bun
Length of output: 1542
🏁 Script executed:
# Check for documentation about the flag in CLI help text
rg -n "max-http-header-count\|maxHeadersCount" packages/bun/src --type=cpp --type=ts -B2 -A2 | head -100Repository: oven-sh/bun
Length of output: 111
🏁 Script executed:
# Look for the default constant definition
rg -n "UWS_HTTP_MAX_HEADERS_COUNT\|BUN_DEFAULT_MAX_HTTP_HEADERS_COUNT" packages/bun-uws/ -C2 --type=h --type=cppRepository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Check if there's any comment explaining why the loop is i < maxHeadersCount - 1
sed -n '750,760p' packages/bun-uws/src/HttpParser.hRepository: oven-sh/bun
Length of output: 573
🏁 Script executed:
# Search for the constant definitions more broadly
rg -n "UWS_HTTP_MAX_HEADERS_COUNT\|BUN_DEFAULT_MAX_HTTP_HEADERS_COUNT" --type=h --type=cpp -C2Repository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Look for CLI flag handling in the main bun code
fd -n "main.cpp\|cli.cpp\|*.cpp" packages/bun | head -20Repository: oven-sh/bun
Length of output: 285
🏁 Script executed:
# Search for where maxHeadersCount is passed to the parser
rg -n "setMaxHeadersCount\|max.*header.*count" --type=cpp --type=ts -B3 -A3 | head -80Repository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Search for the constant definitions
rg "UWS_HTTP_MAX_HEADERS_COUNT|BUN_DEFAULT_MAX_HTTP_HEADERS_COUNT" --type=h --type=cpp -C2Repository: oven-sh/bun
Length of output: 3040
🏁 Script executed:
# Check the entire HttpParser.h to understand the flow
wc -l packages/bun-uws/src/HttpParser.hRepository: oven-sh/bun
Length of output: 93
🏁 Script executed:
# Look at where the loop counter is actually used and what happens at the boundary
sed -n '756,830p' packages/bun-uws/src/HttpParser.h | cat -nRepository: oven-sh/bun
Length of output: 5301
🏁 Script executed:
# Check if the test actually validates the boundary behavior
sed -n '100,130p' test/regression/issue/6982.test.tsRepository: oven-sh/bun
Length of output: 912
🏁 Script executed:
# Look for any comments explaining the off-by-one logic in the loop
sed -n '740,760p' packages/bun-uws/src/HttpParser.h | cat -nRepository: oven-sh/bun
Length of output: 1164
🏁 Script executed:
# See where the terminator is used and if it's counted in the limit
rg -n "string_view(nullptr" packages/bun-uws/src/HttpParser.h -B2 -A2Repository: oven-sh/bun
Length of output: 136
🏁 Script executed:
# Search for the terminator initialization - escape properly
rg -n "string_view.*nullptr.*0" packages/bun-uws/src/HttpParser.h -B2 -A2Repository: oven-sh/bun
Length of output: 672
🏁 Script executed:
# Let's trace through what happens when we parse a request with maxHeadersCount=50
# Check if there's validation that accounts for the off-by-two
python3 <<'EOF'
# Simulate the loop logic
maxHeadersCount = 50
# Array indices:
# [0] = request line (headers[0])
# [1..48] = actual headers (loop runs for i=1 to i<49, so i=1 to 48)
# [49] = terminator slot (headers->key = string_view(nullptr, 0))
loop_iterations = 0
for i in range(1, maxHeadersCount - 1):
loop_iterations += 1
print(f"maxHeadersCount: {maxHeadersCount}")
print(f"Loop range: i from 1 to {maxHeadersCount - 2} (inclusive)")
print(f"Loop iterations: {loop_iterations}")
print(f"Array layout:")
print(f" [0] = request line")
print(f" [1..{maxHeadersCount - 2}] = actual headers ({loop_iterations} slots)")
print(f" [{maxHeadersCount - 1}] = terminator")
print(f"\nEffective user-facing limit: {maxHeadersCount - 2} headers")
print(f"If user sets --max-http-header-count=50, they get 48 actual headers")
EOFRepository: oven-sh/bun
Length of output: 141
🏁 Script executed:
# Check the test to see what it actually validates - the rejection test
sed -n '110,140p' test/regression/issue/6982.test.tsRepository: oven-sh/bun
Length of output: 907
The effective header limit is maxHeadersCount - 2 due to reserved slots for the request line and terminator.
The loop for (unsigned int i = 1; i < maxHeadersCount - 1; i++) combined with headers[0] storing the request line means users can parse only maxHeadersCount - 2 actual headers. With the default of 100, only 98 headers are supported; setting --max-http-header-count=50 allows only 48 headers, not 50.
This appears intentional (per the comment "This cann take the very last header space" at line 823), but the user-facing CLI flag description should clarify this discrepancy, or consider adjusting the internal logic to make the advertised limit match the actual parsing capacity.
🤖 Prompt for AI Agents
In `@packages/bun-uws/src/HttpParser.h` at line 757, The loop for parsing headers
(for (unsigned int i = 1; i < maxHeadersCount - 1; i++)), combined with
headers[0] being used for the request line, means the effective capacity is
maxHeadersCount - 2; update either the parsing logic or the CLI description:
either change the loop bound to i < maxHeadersCount (and adjust any
sentinel/terminator handling and the comment "This cann take the very last
header space") so that setting --max-http-header-count = N allows N headers, or
keep the loop but change the user-facing --max-http-header-count help text to
explicitly state the value reserves two slots (request line + terminator) and
thus supports N-2 actual headers; locate and modify the loop in HttpParser.h,
the headers[0] usage, and the CLI flag description accordingly.
| pub fn setMaxHTTPHeadersCount(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { | ||
| const arguments = callframe.arguments_old(1).slice(); | ||
| if (arguments.len < 1) { | ||
| return globalThis.throwNotEnoughArguments("setMaxHTTPHeadersCount", 1, arguments.len); | ||
| } | ||
| const value = arguments[0]; | ||
| const num = try value.coerceToInt64(globalThis); | ||
| if (num < 0) { | ||
| return globalThis.throwInvalidArgumentTypeValue("maxHeadersCount", "non-negative integer", value); | ||
| } | ||
| if (num == 0) { | ||
| bun.http.max_http_headers_count = std.math.maxInt(u32); | ||
| } else { | ||
| bun.http.max_http_headers_count = @intCast(num); | ||
| } | ||
| return jsc.JSValue.jsNumber(bun.http.max_http_headers_count); | ||
| } |
There was a problem hiding this comment.
Potential integer overflow on large input values.
If num exceeds std.math.maxInt(u32) (4,294,967,295), the @intCast at line 62 will cause a runtime panic. JavaScript can pass values up to Number.MAX_SAFE_INTEGER which is much larger than u32 max.
🔧 Suggested fix: Add upper bound validation
pub fn setMaxHTTPHeadersCount(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
const arguments = callframe.arguments_old(1).slice();
if (arguments.len < 1) {
return globalThis.throwNotEnoughArguments("setMaxHTTPHeadersCount", 1, arguments.len);
}
const value = arguments[0];
const num = try value.coerceToInt64(globalThis);
if (num < 0) {
return globalThis.throwInvalidArgumentTypeValue("maxHeadersCount", "non-negative integer", value);
}
if (num == 0) {
bun.http.max_http_headers_count = std.math.maxInt(u32);
+ } else if (num > std.math.maxInt(u32)) {
+ bun.http.max_http_headers_count = std.math.maxInt(u32);
} else {
bun.http.max_http_headers_count = `@intCast`(num);
}
return jsc.JSValue.jsNumber(bun.http.max_http_headers_count);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn setMaxHTTPHeadersCount(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { | |
| const arguments = callframe.arguments_old(1).slice(); | |
| if (arguments.len < 1) { | |
| return globalThis.throwNotEnoughArguments("setMaxHTTPHeadersCount", 1, arguments.len); | |
| } | |
| const value = arguments[0]; | |
| const num = try value.coerceToInt64(globalThis); | |
| if (num < 0) { | |
| return globalThis.throwInvalidArgumentTypeValue("maxHeadersCount", "non-negative integer", value); | |
| } | |
| if (num == 0) { | |
| bun.http.max_http_headers_count = std.math.maxInt(u32); | |
| } else { | |
| bun.http.max_http_headers_count = @intCast(num); | |
| } | |
| return jsc.JSValue.jsNumber(bun.http.max_http_headers_count); | |
| } | |
| pub fn setMaxHTTPHeadersCount(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { | |
| const arguments = callframe.arguments_old(1).slice(); | |
| if (arguments.len < 1) { | |
| return globalThis.throwNotEnoughArguments("setMaxHTTPHeadersCount", 1, arguments.len); | |
| } | |
| const value = arguments[0]; | |
| const num = try value.coerceToInt64(globalThis); | |
| if (num < 0) { | |
| return globalThis.throwInvalidArgumentTypeValue("maxHeadersCount", "non-negative integer", value); | |
| } | |
| if (num == 0) { | |
| bun.http.max_http_headers_count = std.math.maxInt(u32); | |
| } else if (num > std.math.maxInt(u32)) { | |
| bun.http.max_http_headers_count = std.math.maxInt(u32); | |
| } else { | |
| bun.http.max_http_headers_count = `@intCast`(num); | |
| } | |
| return jsc.JSValue.jsNumber(bun.http.max_http_headers_count); | |
| } |
🤖 Prompt for AI Agents
In `@src/bun.js/node/node_http_binding.zig` around lines 49 - 65, The
setMaxHTTPHeadersCount function can panic when num exceeds u32 max; add an
upper-bound check after coercing num to ensure num <= std.math.maxInt(u32) and
return globalThis.throwInvalidArgumentTypeValue (or a similar JS error) if it’s
larger, otherwise safely assign bun.http.max_http_headers_count using `@intCast`;
reference the symbols setMaxHTTPHeadersCount, num, std.math.maxInt(u32),
`@intCast`, and bun.http.max_http_headers_count when implementing the validation
and error return.
| // Read server URL from stdout | ||
| const reader = proc.stdout.getReader(); | ||
| const { value } = await reader.read(); | ||
| reader.releaseLock(); | ||
| const url = new TextDecoder().decode(value).trim(); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider using a more robust approach to read the server URL.
Reading a single chunk from the stream may be fragile if the URL happens to be split across buffer boundaries. Consider using a line-based approach or reading until a newline is found.
♻️ Suggested improvement
- // Read server URL from stdout
- const reader = proc.stdout.getReader();
- const { value } = await reader.read();
- reader.releaseLock();
- const url = new TextDecoder().decode(value).trim();
+ // Read server URL from stdout (read until newline)
+ let urlData = "";
+ const reader = proc.stdout.getReader();
+ const decoder = new TextDecoder();
+ while (!urlData.includes("\n")) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ urlData += decoder.decode(value, { stream: true });
+ }
+ reader.releaseLock();
+ const url = urlData.trim();Also applies to: 125-129
🤖 Prompt for AI Agents
In `@test/regression/issue/6982.test.ts` around lines 81 - 85, The current logic
reads a single chunk from proc.stdout.getReader() and decodes value into url,
which can break if the URL is split across chunks; change to a loop that
repeatedly reads from reader.read(), appends decoded chunks into a buffer, and
stops when a newline is encountered (or EOF), then trim to produce url; apply
the same fix to the second occurrence that also reads stdout (lines referenced
around the second block).
|
Superseded by #26130 |
Summary
--max-http-header-countCLI flag to configure the maximum number of HTTP headers allowed per request (default: 100)http.maxHeadersCountgetter/setter for Node.js compatibilityTest plan
bun bd test test/regression/issue/6982.test.tspassesUSE_SYSTEM_BUN=1(feature doesn't exist in current release)bun --max-http-header-count=200 -e "console.log(require('http').maxHeadersCount)"outputs200--max-http-header-count=200--max-http-header-count=50(returns 431)Fixes #6982
🤖 Generated with Claude Code