Skip to content

install: honor BUN_CONFIG_MAX_HTTP_REQUESTS - #38744

Open
robobun wants to merge 3 commits into
mainfrom
farm/28e362eb/install-honor-max-http-requests-env
Open

robobun wants to merge 3 commits into
mainfrom
farm/28e362eb/install-honor-max-http-requests-env

Conversation

@robobun

@robobun robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • BUN_CONFIG_MAX_HTTP_REQUESTS has no effect on bun install: with it set to 8, a loopback registry still sees 64 requests in flight. Only --network-concurrency works. Same on 1.3.14; the docs list the variable as applying to bun install.
  • PackageManagerOptions::load (src/install/PackageManager/PackageManagerOptions.rs:661) calls async_http::load_env, which stores the variable into MAX_SIMULTANEOUS_REQUESTS. PackageManager::init (src/install/PackageManager.rs, formerly line 2276) then unconditionally stored --network-concurrency or the 64 default over it.
  • With no retry or Retry-After handling for 429s in bun install, this variable is the documented way to get past a rate limiting registry, so the documented mitigation was a no-op.

Fix

  • PackageManager::init now stores the install default (64, or the proxy default) before options.load, and after options.load stores only an explicitly passed --network-concurrency. Precedence is therefore flag, then env var, then default.
  • The fixing line is the if let Some(network_concurrency) guard on the post-options.load store; the values themselves are unchanged (--network-concurrency 0 still clamps to 1, an invalid or zero env value still logs and falls back to the default, the same as under fetch).
  • The bun install docs row for BUN_CONFIG_MAX_HTTP_REQUESTS now states the install default and that --network-concurrency overrides the variable.
  • Test: test/cli/install/bun-install.test.ts, "bun install with ... keeps exactly N requests in flight". The existing --network-concurrency=5 case became an it.each row (same input; its 51-dependency package.json is now generated). Two new rows: BUN_CONFIG_MAX_HTTP_REQUESTS=5, and --network-concurrency=2 together with BUN_CONFIG_MAX_HTTP_REQUESTS=50, which pins the precedence. The registry stub holds every manifest request until the cap is reached, waits a bounded window for a client that ignores the cap to open more connections, then releases them all, so the peak must equal the cap.
  • bun bd test test/cli/install/bun-install.test.ts -t "requests in flight": 3 pass with the fix; the env var row fails with Received: 51 on the unfixed debug build and on release 1.4.0.
  • Full bun-install.test.ts on the fixed build: the only failures are the 14 tests that need public internet (bitbucket/gitlab/vercel URLs) plus should support --registry CLI flag, and all of them fail identically on the unfixed build in this environment.

Background

  • MAX_SIMULTANEOUS_REQUESTS (src/http/AsyncHTTP.rs) is the process-wide cap on in-flight HTTP requests. The HTTP thread reads it on every drain, so whatever value is in it when the first request is scheduled is what applies; bun install starts the HTTP thread later in init, after both stores.
  • async_http::load_env is the shared parser for BUN_CONFIG_MAX_HTTP_REQUESTS; the runtime (bun run, fetch) calls it directly on top of the static's 256 default, and bun install reaches it through options.load. Layering the install default underneath it, instead of on top of it, is what makes the same function serve both.
Probe of the report's matrix against the fixed debug build (80 leaf deps, loopback stub holding each request 30 ms)
default        peak in-flight=64 exit=1
env=8          peak in-flight= 8 exit=1
env=abc        peak in-flight=64 exit=1 error: BUN_CONFIG_MAX_HTTP_REQUESTS value "abc" is not a valid integer between 1 and 65535
env=0          peak in-flight=64 exit=1 warn: BUN_CONFIG_MAX_HTTP_REQUESTS value must be a number between 1 and 65535
flag=8         peak in-flight= 8 exit=1
env=4 flag=8   peak in-flight= 8 exit=1

Before the fix, env=8 and env=4 flag=8 read 64 and 8 respectively; the other rows are unchanged.


no test proof · iteration 7 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/install/bun-install.test.ts

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:32 AM PT - Aug 22nd, 2026

❌ @robobun, your commit 957ee08 has 2 failures in Build #103387 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 38744

That installs a local version of the PR into your bun-38744 executable, so you can run:

bun-38744 --bun

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reproduced and fixed; ready for review.

  • Reproduced with a loopback registry stub: BUN_CONFIG_MAX_HTTP_REQUESTS=5 bun install on 1.4.0 and on an unfixed debug build of main put all 51 manifest requests in flight at once; --network-concurrency 5 capped at 5.
  • With this branch the env var caps at exactly 5, and --network-concurrency still wins when both are set (test/cli/install/bun-install.test.ts, the "keeps exactly N requests in flight" rows).
  • Rebased onto main twice (diff unchanged) to stay mergeable.
  • Latest build: the only non-retried failures are test/cli/install/bun-pm.test.ts and test/cli/install/bun-prune.test.ts, whose inline snapshots on main still expect the summary wording from before pm: polish dedupe, prune, pm ls and pm licenses output #38952 ("(5)" vs "(5 installed)"). Both fail the same way without this diff and are reported separately.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Your included review limit has been reached.

You’re in a promotional period — use the checkbox below to run this review for free:

  • Run review for free

On-demand reviews are free for the next 29 days. After that, they cost $0.25 per reviewed file.

How can I continue?

Run this review now using the option above, or comment @coderabbitai review --use-credits.

You can also wait for the limit to reset (next review available in 16 minutes), then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8237b51a-33ed-4e81-9a89-bf9cc3e99248

📥 Commits

Reviewing files that changed from the base of the PR and between 64092d4 and 957ee08.

📒 Files selected for processing (3)
  • docs/runtime/environment-variables.mdx
  • src/install/PackageManager.rs
  • test/cli/install/bun-install.test.ts

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

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — small, well-scoped precedence fix.

What was reviewed:

  • Traced the three MAX_SIMULTANEOUS_REQUESTS stores against async_http::load_env (AsyncHTTP.rs:205-234) — invalid/zero env values still fall through to the pre-stored install default, and the .max(1) clamp on --network-concurrency 0 is preserved.
  • Checked that moving env.has_http_proxy() before options.load() is safe: options.load only reads from the already-populated DotEnvLoader and does not load additional env sources that could add proxy vars.
  • Test refactor: ctx.requested is the per-context counter incremented by the shared dummy registry (dummy.registry.ts:350), env is bunEnv aliased at import, and the it.each rows cover flag-only, env-only, and flag-overrides-env.
Extended reasoning...

Overview

Three files: src/install/PackageManager.rs reorders when MAX_SIMULTANEOUS_REQUESTS is written during PackageManager::init so that BUN_CONFIG_MAX_HTTP_REQUESTS (parsed inside options.load → async_http::load_env) is no longer clobbered by the install default; test/cli/install/bun-install.test.ts converts the existing --network-concurrency=5 test into an it.each with two additional rows for the env var alone and for flag-overrides-env; and docs/runtime/environment-variables.mdx clarifies the install default (64) and precedence.

Security risks

None. This adjusts a process-wide concurrency cap for outbound HTTP during bun install. No auth, crypto, path handling, or untrusted-input parsing is touched. The env var parser itself (load_env) is unchanged and already bounds-checks to 1..=65535.

Level of scrutiny

Low-to-medium. The Rust change is a mechanical reordering of three .store(.., Ordering::Relaxed) calls on a static atomic during single-threaded init, before the HTTP thread starts. I verified: (1) load_env only stores when the env value parses to a nonzero u16, so an invalid/zero value now falls back to the install default that was pre-stored — matching the PR description's probe matrix; (2) cli.network_concurrency is Option<u16>, so usize::from(n.max(1)) is the same clamp as the removed n.max(1) as usize; (3) env.has_http_proxy() reads http_proxy/HTTP_PROXY/https_proxy/HTTPS_PROXY from the already-loaded DotEnvLoader, and options.load (PackageManagerOptions.rs:403-672) does not mutate those keys, so calling it before options.load returns the same result as before.

Other factors

The test refactor follows REVIEW.md harness conventions: await using proc, concurrent pipe drain via Promise.all, {...env, ...extraEnv} spread over bunEnv, and a combined-object toEqual assertion. The dropped > 20 throw inside the stub is safe to remove because a thrown handler would have returned a 500 and triggered install-side retries, inflating ctx.requested past 51 — the maxConcurrentRequests <= cap assertion already guards the invariant. The 10 ms Bun.sleep is simulated registry latency (needed for requests to overlap so the peak counter is meaningful), not a wait-for-condition; it's commented as such. The PR description shows the env-var row failing with Received: 51 on the unfixed build and passing with the fix, satisfying the fails-for-the-right-reason requirement.

@robobun
robobun force-pushed the farm/28e362eb/install-honor-max-http-requests-env branch from 00df52c to 3ebff88 Compare August 15, 2026 03:26
Comment thread src/install/PackageManager.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — reorders the three MAX_SIMULTANEOUS_REQUESTS stores so the env var isn't clobbered by the install default.

What was reviewed:

  • Traced options.load → async_http::load_env (AsyncHTTP.rs:205–234): only stores when the env var parses to a nonzero u16, so invalid/zero falls through to the install default set just before it.
  • env.has_http_proxy() moved before options.load — env is passed by shared ref, so the proxy check can't differ between the old and new position; http_thread::init still runs after all three stores.
  • Test refactor: ctx.requested is the harness's per-context counter (dummy.registry.ts:350), the dropped urls assertion was dead, and the > 20 throw would have caused 500 retries — the maxConcurrentRequests <= cap assertion is the real guard.
Extended reasoning...

Overview

The PR fixes BUN_CONFIG_MAX_HTTP_REQUESTS being ignored by bun install. Three files: PackageManager.rs reorders atomic stores in init (default before options.load, CLI flag after, guarded by if let Some); bun-install.test.ts converts the existing --network-concurrency=5 test into an it.each with two new rows (env-only, flag-overrides-env); the docs table row for the env var now states the install default (64) and flag precedence.

Security risks

None. This is init-time ordering of atomic stores to a process-wide concurrency cap. The values themselves are unchanged (same clamp, same default constants), and load_env already validates the env var as a u16 in [1, 65535].

Level of scrutiny

Low-to-medium. The change is a mechanical reordering of three stores with a clear invariant (flag > env > default). I verified: load_env is reached via PackageManagerOptions::load at line 661 and only writes when the env value is valid; env is a &DotEnvLoader throughout so has_http_proxy() returns the same value at the earlier position; the HTTP thread starts at http_thread::init further down, so all three stores land before any request is scheduled. The usize::from(network_concurrency.max(1)) matches the old .max(1) as usize semantics (the type is u16 so usize::from is lossless).

Other factors

The test changes are a strict improvement over the original: the dead urls assertion and unused stdin: "pipe" are gone, pipes are drained concurrently with Promise.all, await using handles cleanup, and ctx.requested reuses the harness counter that other tests in this file already rely on. The Bun.sleep(10) in the stub is simulated registry latency (needed to make requests overlap so peak concurrency is observable) and is commented as such — not a wait-for-condition sleep. The comment-cop bot's feedback about the paragraph-long comment was addressed in 3a2a9c8. The one CI failure (test-http-chunk-problem.js) is unrelated and was picked up via rebase onto #38726.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up from #38759, which fixes the --network-concurrency help text (48 -> 64) and adds a test for the default in the same file, a little above the --network-concurrency=5 test this PR turns into a table.

The two branches merge cleanly right now. #38759 puts its registry stub in a module-level helper, expectInstallInFlightLimit(ctx, limit), which holds every manifest request open and asserts that exactly limit requests are in flight (with limit + 1 dependencies, so the extra one has to wait for a slot). It already clears BUN_CONFIG_MAX_HTTP_REQUESTS from the child environment. If that lands first, the rows here could become calls to it with args/env options added, instead of the Bun.sleep(10) + toBeLessThanOrEqual(cap) stub: that asserts the exact cap, so the --network-concurrency=2 over BUN_CONFIG_MAX_HTTP_REQUESTS=50 row would also distinguish "2" from "bun used fewer", and it does not depend on the burst overlapping within 10ms on a loaded debug runner. Either way, it would be good to end up with one stub in the file rather than two.

PackageManager::init stored the --network-concurrency value (or the 64
default) into MAX_SIMULTANEOUS_REQUESTS after options.load had already
applied BUN_CONFIG_MAX_HTTP_REQUESTS, so the env var never had an effect
on bun install. Store the install default before options.load and only
the explicit --network-concurrency value after it, so the precedence is
flag, then env var, then default.
@robobun
robobun force-pushed the farm/28e362eb/install-honor-max-http-requests-env branch from 3a2a9c8 to 3e547e8 Compare August 22, 2026 07:23

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — focused fix that reorders the MAX_SIMULTANEOUS_REQUESTS stores so BUN_CONFIG_MAX_HTTP_REQUESTS (applied inside options.load → load_env) is no longer clobbered by the install default, with --network-concurrency still winning when passed.

What was reviewed:

  • Verified load_env in src/http/AsyncHTTP.rs writes MAX_SIMULTANEOUS_REQUESTS and is called from PackageManagerOptions::load, so storing the default before it and the flag after it gives flag > env > default.
  • Checked that moving env.has_http_proxy() to before options.load is safe — it reads only the already-populated DotEnvLoader, not anything options.load sets up.
  • Confirmed cli.network_concurrency is Option<u16>, so the .max(1) clamp and usize::from conversion are unchanged from the old as usize cast.
  • Test refactor: the three it.each rows spread extraEnv after bunEnv (which does not set BUN_CONFIG_MAX_HTTP_REQUESTS), and each row runs in its own withContext under describe.concurrent, so no shared-counter races.
Extended reasoning...

Overview

The PR fixes bun install to honor BUN_CONFIG_MAX_HTTP_REQUESTS. Three files touched: src/install/PackageManager.rs (reorder two stores around options.load), test/cli/install/bun-install.test.ts (convert the existing --network-concurrency=5 test into an it.each with two new rows for the env var and for flag-overrides-env precedence), and a one-line clarification in docs/runtime/environment-variables.mdx.

Correctness

I traced the flow: PackageManagerOptions::load → bun_http::async_http::load_env (line 701 of PackageManagerOptions.rs), which parses BUN_CONFIG_MAX_HTTP_REQUESTS and stores into the MAX_SIMULTANEOUS_REQUESTS atomic (AsyncHTTP.rs:233). The old code then unconditionally overwrote that with the flag-or-64 default. The new code stores the default first, lets load_env overwrite it if the env var is set, then overwrites again only if --network-concurrency was explicitly passed (Option<u16>). Invalid/zero env values still fall back to the default because load_env returns early without storing in those cases. env.has_http_proxy() only reads the DotEnv map, which is populated before init runs, so calling it before options.load is equivalent. Nothing between the old and new store locations reads the atomic — the HTTP thread starts later in init.

Security risks

None. This adjusts an integer concurrency cap read from an env var the process already trusts; load_env already validates it as a u16 in [1, 65535].

Level of scrutiny

Low-to-medium. It's a small reordering of existing stores in single-threaded init code, with no new allocation, FFI, or JS interaction. The test change is a mechanical table-ification of an existing test plus two new rows; the removed > 20 throw in the stub was redundant with the toBeLessThanOrEqual assertion (and would have triggered retries on 500, muddying the count).

Other factors

The comment-cop feedback was addressed (the precedence comment is now a single line). The robobun note about #38759's expectInstallInFlightLimit helper is a forward-looking consolidation suggestion, not a blocker — the branches merge cleanly and this test's toBeLessThanOrEqual(cap) assertion still fails hard on the unfixed build (51 vs 5). CI passed on Linux and Windows; the pending darwin-arm64 shards are an agent-queue backlog unrelated to the change.

Hold every manifest request until the cap is reached, wait a bounded
window for a client that ignores the cap to open more connections, then
release. The peak must equal the cap, so the rows also catch a client
that uses fewer slots than configured.
@robobun

robobun commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Took the exact-cap idea in 957ee08: the stub now holds every manifest request until cap are in flight, waits a bounded window so a client that ignores the cap has time to open more connections, then releases them all. peakInFlight must equal cap, so the --network-concurrency=2 over BUN_CONFIG_MAX_HTTP_REQUESTS=50 row now distinguishes 2 from fewer. The unfixed build still fails the env var row with 51 in flight.

Holding until exactly cap and releasing at once is not enough on its own: the held handlers resume in the microtasks after the cap-th request and are gone before the next connection is read, so a client with no cap also peaks at cap. The window is what catches the extra connections.

The stub stays inline in the it.each body, so the two branches still merge cleanly. Whichever lands second can fold the rows into one module-level helper.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — small, well-scoped fix that reorders the MAX_SIMULTANEOUS_REQUESTS stores around options.load() so BUN_CONFIG_MAX_HTTP_REQUESTS is honored, with --network-concurrency still winning.

What was reviewed:

  • Verified options.load() → async_http::load_env() writes the env var into MAX_SIMULTANEOUS_REQUESTS and falls through on invalid/zero input, so the pre-store install default (64 / proxy) survives — matches the probe matrix in the description.
  • Confirmed env.has_http_proxy() is safe to call before options.load() (env is already loaded; BUN_MANIFEST_CACHE is read from it two lines earlier), and both stores land before the HTTP thread is started later in init.
  • Test: the hold-until-released stub makes peakInFlight an exact count (single-threaded handler, requests parked on await released); the Bun.sleep(50) after capReached carries the required justification comment; pipes drained concurrently; bunEnv doesn't set BUN_CONFIG_MAX_HTTP_REQUESTS so the flag-only row is clean.
Extended reasoning...

Overview

Three files: src/install/PackageManager.rs splits the single unconditional MAX_SIMULTANEOUS_REQUESTS.store(...) (which ran after options.load() and always stomped the env-var write inside load_env) into (a) a default store before options.load() and (b) a conditional CLI-flag store after it. test/cli/install/bun-install.test.ts converts the existing --network-concurrency=5 test into an it.each with two new rows (env-var only; flag overriding env-var) and tightens the assertion from toBeLessThanOrEqual to toBe(cap). docs/runtime/environment-variables.mdx documents the install default of 64 and the flag override.

Security risks

None. This is a config-precedence fix for an HTTP concurrency knob. No auth, crypto, path handling, or untrusted-input parsing is touched. Lowering concurrency (the intended use) is strictly safer.

Level of scrutiny

Low-to-moderate. The Rust change is a pure reordering of atomic stores in single-threaded init code, preserving all existing values (.max(1) clamp on the flag, proxy default, 64 default). I traced the call chain: PackageManagerOptions::load at line ~701 calls bun_http::async_http::load_env, which parses BUN_CONFIG_MAX_HTTP_REQUESTS and stores it only when valid and non-zero — so the install default stored just before survives on invalid input, matching the PR's probe output. The HTTP thread is started later in init (after the CA setup that follows the removed block), so ordering relative to consumers is unchanged.

Other factors

  • The comment-cop bot's "paragraph-long comment" flag was resolved in 3e547e8 — the comment is now a single line.
  • The robobun note about #38759 (a shared expectInstallInFlightLimit helper) is future-consolidation guidance, not a blocker; the two branches merge cleanly per that comment.
  • The rewritten test is strictly stronger than the original: exact toBe(cap) instead of <= 5, generated 51-dep package.json instead of a hardcoded list, and the request-holding stub removes the timing dependency the original Bun.sleep(10) had. The remaining Bun.sleep(50) sits after the cap is provably reached and only detects over-cap sends; the accompanying comment satisfies the review rule for justified sleeps at that threshold.
  • Verified bunEnv (which env aliases) does not set BUN_CONFIG_MAX_HTTP_REQUESTS, so the extraEnv: {} row is not contaminated.
  • CI: Linux and Windows lanes green per the status comment; darwin arm64 shards are queued on infra, unrelated to this diff.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants