Install progress: move counter to left of package name to reduce layo… - #1
Draft
kepae wants to merge 28 commits into
Draft
Install progress: move counter to left of package name to reduce layo…#1kepae wants to merge 28 commits into
kepae wants to merge 28 commits into
Conversation
…ven-sh#29021) Two pre-existing `fs.Stats` constructor bugs in `NodeFSStatBinding.cpp`, both found by the review bot on oven-sh#28989 (no overlap with that diff). ## 1. `Stats(...)` without `new` scrambles fields `callJSStatsFunction` wrote `putDirectOffset` slots 0–9 in Node's constructor-argument order (`dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks`), but the structure's slot layout (`createJSStatsObjectStructure`) is `dev, ino, mode, nlink, uid, gid, rdev, size, blksize, blocks` — so 8 of 10 integer fields landed under the wrong property: ```js const s = require("fs").Stats(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); s.ino // 1 (the "mode" argument) s.size // 7 (the "ino" argument) ``` `new Stats(...)` was already correct (uses `putDirect` by name), as was `fs.statSync` (`Bun__createJSStatsObject` uses the right offsets). Fix: reorder the ten `putDirectOffset` calls to match the structure. ## 2. `statSync(p) instanceof Stats` returns `false` `initJSStatsClassStructure` created one `JSStatsPrototype` for `Stats.prototype` / the constructor, and `createJSStatsObjectStructure` created a **second** one baked into the instance structure. So every instance's `[[Prototype]]` was a different object than `Stats.prototype`: ```js statSync(".") instanceof Stats // bun: false, node: true Object.getPrototypeOf(statSync(".")) === Stats.prototype // bun: false, node: true ``` Methods like `.isFile()` still worked because both prototypes were real `JSStatsPrototype` instances — just not the same one. Same issue for `BigIntStats`. Fix: `createJS{,BigInt}StatsObjectStructure` now take the prototype as a parameter instead of creating their own. ## Tests `test/js/node/fs/fs-stats-constructor.test.ts` covers both: field ordering for `new Stats(...)` and `Stats(...)`, and `instanceof` / prototype identity for `statSync`, `new Stats`, `Stats()`, and `statSync({bigint:true})`. All fail on released bun, pass on this branch.
Simpler alternative to oven-sh#28957. ## What `fs.statSync(file).ino` returns `9223372036854775807` (`INT64_MAX`) for any file whose inode is `>= 2^63`, because `Stat.zig`'s `clampedInt64` saturates every `u64` stat field before handing it to C++. Every file on an NFS mount with high 64-bit inodes collapses to the same number. ## Fix Make `PosixStat` mirror libuv's `uv_stat_t` — every numeric field is `u64` — and pass those `u64` values straight through to C++, which does the same two casts Node does (`src/node_file-inl.h` `FillStatsArray`): - **`Stats`**: `jsNumber(uint64_t)` → `static_cast<double>` (Node fills a `Float64Array`) - **`BigIntStats`**: `JSBigInt::createFrom(static_cast<int64_t>(uint64_t))` (Node fills a `BigInt64Array`) `clampedInt64` is deleted; there is no per-field special-casing. ## vs oven-sh#28957 | | oven-sh#28957 | this PR | |---|---|---| | `PosixStat` field types | platform-dependent | all `u64` (= `uv_stat_t`) | | conversion helpers in `Stat.zig` | `clampedInt64` + `toF64` + `toI64` | none | | fields fixed | `dev`/`ino`/`rdev` only | all 10 (matches Node uniformly) | | test helper | ~30 LoC Zig + 18 LoC TS overloads | 5 LoC Zig + 1 LoC TS | | FUSE test (skipped in CI) | 267 LoC | dropped | | net diff | +471 / −12 | +143 / −151 | ## Tests `test/js/node/fs/fs-stats-truncate.test.ts` drives the real `statToJS` path with `ino` values at `0`, `2^53-1`, `2^63-1`, `2^63`, `9225185599684229422` (the NFS inode from the report), and `2^64-1`, and asserts the Number path matches `Number(u64)` and the BigInt path matches `BigInt.asIntN(64, u64)` — exactly what Node produces. ``` bun bd test test/js/node/fs/fs-stats-truncate.test.ts # 3 pass bun bd test test/js/node/fs/fs.test.ts -t stat # 16 pass, 1 skip ``` --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…or' (oven-sh#29029) Recent builds have been showing flaky `annotation error on <target>` entries (e.g. builds #44422, #44397). These come from `reportAnnotationToBuildKite` in `scripts/utils.mjs`, which spawns `buildkite-agent annotate` with a 5-second `spawnSync` timeout. When the agent doesn't finish in 5s, Node SIGTERMs it, `status` comes back `null`, and the old fallback posted a second annotation titled "annotation error" whose body was whatever stderr had been captured so far — in every observed case just `INFO Reading annotation body from STDIN`, which is useless. The Buildkite status page being green doesn't protect against this: `buildkite-agent`'s HTTP client has its own retry loop with exponential backoff, so a single transient failure on one request (TCP reset, TLS hiccup, a lone 5xx, or per-build rate limiting when many parallel test jobs append to the shared `flaky` context at once) makes the agent sleep several seconds before retrying internally — already past our 5s kill. None of that registers as a service outage. This change raises the timeout to 30s (enough for one internal agent retry cycle), retries the original annotation once on failure instead of posting a content-free "annotation error", and logs-and-continues rather than `throw`ing if the retry also fails — the throw path could abort `runner.node.mjs` mid-suite over what is ultimately a cosmetic reporting step.
`getExecPathFromBuildKite` runs `buildkite-agent artifact download` via
`spawnSafe` with a 60 s timeout. On timeout `spawnSafe` kills the
process and returns `{ error: "timeout" }` — it doesn't throw — and the
caller doesn't check it. The loop then picks whichever `bun*.zip` made
it to disk and continues.
On a slow win-aarch64 VM (build #44517, 38 MiB in 52 s) the 149 MiB
`*-profile.zip` never finished, so the runner silently fell back to the
release `bun.exe` instead of `bun-profile.exe`. That in turn made
`which.test.ts` fail because `basename(process.execPath)` became
`"bun.exe"`, which `bootstrap.ps1` bakes into `C:\Windows\System32\` on
the v14 image.
Bump the timeout to 120 s and throw if it's hit so the job fails with a
clear message instead of running the wrong binary.
### What does this PR do? On NixOS, `autoPatchelfHook` rewrites bun's `PT_INTERP` from `/lib64/ld-linux-x86-64.so.2` to a `/nix/store/<hash>-glibc-.../lib/...` path. `bun build --compile` copies the running binary verbatim, so the output inherits that store path and only runs on the exact same Nix generation. This adds `ElfFile.normalizeInterpreter()`: when injecting the `.bun` section, if `PT_INTERP` starts with `/nix/store/` or `/gnu/store/`, rewrite it to the standard FHS path by mapping the linker basename: | basename | rewritten to | |---|---| | `ld-linux-x86-64.so.2` | `/lib64/ld-linux-x86-64.so.2` | | `ld-linux-aarch64.so.1` | `/lib/ld-linux-aarch64.so.1` | | `ld-musl-x86_64.so.1` | `/lib/ld-musl-x86_64.so.1` | | `ld-musl-aarch64.so.1` | `/lib/ld-musl-aarch64.so.1` | Store paths are always longer than the FHS path (32-char hash + pname + `/lib/` alone exceeds any of these), so this is an in-place shrink — write the new string, zero-fill, update `p_filesz`/`p_memsz`. No segment moves. **No effect on non-Nix users** — gated on the store-path prefix; unknown basenames are left untouched. Fixes oven-sh#24742 ### How did you verify your code works? - `bun bd` builds, `bun run zig:check-all` passes on all targets - New `test/regression/issue/24742.test.ts` (Linux-only, requires `patchelf`): copies bun, sets a fake `/nix/store/...` interpreter, runs `bun build --compile --compile-executable-path=<patched>`, asserts output's `PT_INTERP` is the FHS path and the binary executes - Test correctly skips on macOS locally; Linux CI will exercise the pass case --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## What & why The WebSocket HTTP upgrade client received host, path, protocol, headers, proxy host, proxy auth, proxy headers and target authorization from C++ as `ZigString` wrappers over the underlying `WTF::StringImpl`. When a `WTFStringImpl` was **not** 8-bit ASCII (either Latin1 with high bytes, or UTF-16), calling `.slice()` on the `ZigString` in Zig returned raw Latin1 / UTF-16 code units — not UTF-8. Those bytes were then substituted into a printf-style format string inside `buildRequestBody`, and the resulting garbage length could cause heap corruption in mimalloc during `std.fmt.allocPrint`: ``` _mi_heap_realloc_zero (vendor/mimalloc/src/alloc.c) Io.Writer.Allocating.drain (vendor/zig/lib/std/Io/Writer.zig) Io.Writer.alignBuffer (vendor/zig/lib/std/Io/Writer.zig:525) http.websocket_client.WebSocketUpgradeClient.buildRequestBody (Writer.zig:1007) Bun__WebSocketHTTPClient__connect (WebSocketUpgradeClient.zig:140) WebCore::WebSocket::connect (WebSocket.cpp:655) WebCore::constructJSWebSocket3 (WebSocket.cpp:352) ``` ## Fix - Change the `Bun__WebSocketHTTPClient__connect` / `…HTTPSClient__connect` FFI signature to take `BunString*` instead of `ZigString*`. - In `WebSocket::connect()` in C++, build `BunString` wrappers via `Bun::toString(WTF::String&)` so the encoding tag is preserved end-to-end (materialize URL `.host()` as a `WTF::String` first; keep iterator key/value `WTF::String`s alive in side vectors for the duration of the call). - On the Zig side, decode every input up front with `bun.String.toUTF8(allocator)`, which borrows the 8-bit ASCII backing when possible and only allocates a UTF-8 copy for non-ASCII Latin1 / UTF-16 inputs. - Introduce `Headers8Bit` to hold the decoded header name/value slices alongside the underlying `ZigString.Slice`s for deterministic cleanup. ## Tests `test/js/web/websocket/websocket-utf16-headers.test.ts` covers: - Latin1-with-high-bytes header values are sent as proper UTF-8 (verified by inspecting the upgrade request bytes received by a raw TCP server). - Non-ASCII path segments in the target URL. - Proxy + custom TLS config + Latin1 headers (the exact combination from the crash report). - Many Latin1 headers through a proxy. - Latin1 proxy header values. Existing `websocket-custom-headers.test.ts` (10/10) and `websocket-proxy.test.ts` (22/22 passing, 3 pre-existing failures unrelated to these changes) still pass. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
…oven-sh#28785) ## Problem When the current working directory is inside a directory that lacks read permission (mode `0111`), `process.env` becomes completely empty — not just missing `.env` file vars, but ALL environment variables including those passed via `execve`. ### Root cause In `runEnvLoader()`, `loadProcess()` (which reads `std.os.environ` into the env map) was called **after** `readDirInfo()` (which walks parent directories for project root discovery). When `readDirInfo()` failed due to EACCES on the CWD, the function returned early and `loadProcess()` was never called, leaving `process.env` completely empty. ### Reproduction ```dockerfile FROM oven/bun:1.3.11 AS builder WORKDIR /build RUN echo 'for (const [k,v] of Object.entries(process.env)) { console.log(k + "=" + v); }' > test.ts \\ && bun build --compile test.ts --outfile /build/test-bin FROM redhat/ubi8-minimal USER root COPY --from=builder /build/test-bin /usr/local/bin/test-bin RUN mkdir -p /noaccess/subdir && chmod 111 /noaccess RUN microdnf install -y shadow-utils && microdnf clean all RUN useradd -m testuser USER testuser CMD echo "=== From /tmp (works) ===" \\ && cd /tmp && MY_VAR=visible test-bin \\ && echo "=== From /noaccess/subdir (BROKEN) ===" \\ && cd /noaccess/subdir && MY_VAR=visible test-bin ``` ## Fix 1. **`src/transpiler.zig`**: Move `loadProcess()` before `readDirInfo()` so OS-inherited environment variables are always loaded, regardless of whether directory traversal succeeds. `.env` file loading still depends on successful directory listing (which is correct — can't load files from an unreadable directory). 2. **`src/resolver/resolver.zig`**: Replace `unreachable` at the end of the directory queue loop with `return null`. When all directories in the queue are skipped (e.g. every directory including the CWD returned EACCES), the caller can now degrade gracefully instead of hitting undefined behavior. ## Verification - `USE_SYSTEM_BUN=1 bun test test/cli/run/env.test.ts -t 'process.env is preserved'` → **FAIL** (process.env empty) - `bun bd test test/cli/run/env.test.ts -t 'process.env is preserved'` → **PASS** (process.env populated) --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Alistair Smith <hi@alistair.sh>
…#28977) Split out from oven-sh#18111 by @martinamps. `--elide-lines` currently exits with an error when stdout is not a terminal, which breaks scripts that pass the flag and run in both interactive and CI/hook contexts. The flag is already a no-op in this case (the elision code only runs in the TTY redraw path), so the error serves no purpose. This removes it. Fixes oven-sh#16286 --------- Co-authored-by: Martin Amps <m@rtin.so> Co-authored-by: Martin Amps <mamps@anthropic.com> Co-authored-by: robobun <robobun@bun.sh>
…en-sh#28926) `Bun.dns.setServers` called `toInt32()` on the family and port elements of each server triple. The internal `toInt32()` helper falls through to `JSC__JSValue__toInt32`, which calls `JSValue::asInt32()` and asserts `isInt32()`. Passing a double (e.g. `-9007199254740991`) or leaving the port out (so the index returns `undefined`) tripped the assertion: ``` ASSERTION FAILED: isInt32() JavaScriptCore/JSCJSValue.h(994) : int32_t JSC::JSValue::asInt32() const ``` Repro: ```js Bun.dns.setServers([[-9007199254740991, 15473]]); ``` Switch to `coerceToInt32`, which uses JSC's full numeric coercion. The existing `family != 4 and family != 6` check still rejects invalid families with a proper `TypeError`. Found by Fuzzilli.
Fixes a segfault when reading `.fd` on the result of `Bun.listen({ tls:
{ ... } })`.
`Listener.getFD` was calling `uws_listener.socket(true).fd()` for TLS
listeners. For `is_ssl=true`, the uSockets wrapper
`us_internal_ssl_socket_get_native_handle` returns `s->ssl`, and `fd()`
then calls `SSL_get_fd()` on it. But a listen socket has no SSL object —
SSL is per-connection — so `s->ssl` is uninitialized memory (ASAN poison
`0xbebebe...`) and the call segfaults.
Listen sockets always have a plain poll fd regardless of TLS, so get it
via the non-SSL path.
```
oven-sh#3 SSL_get_rfd (ssl=0xbebebe0000000018)
oven-sh#4 SSL_get_fd (ssl=0xbebebe0000000018)
oven-sh#5 deps.uws.socket.NewSocketHandler(true).fd () at src/deps/uws/socket.zig:283
bun.js.api.bun.socket.Listener.getFD at src/bun.js/api/bun/socket/Listener.zig:532
```
Repro (also triggered when `console.log()` introspects the listener):
```js
const s = Bun.listen({
hostname: "localhost", port: 0,
socket: { data(){}, open(){}, close(){} },
tls: { passphrase: "abc" },
});
console.log(s.fd);
```
Found by Fuzzilli.
---------
Co-authored-by: robobun <robobun@users.noreply.github.com>
Adds a short "Code Review Self-Check" section before "Important Development Notes": - Justify each non-obvious choice before writing it (research first, don't write-then-justify) - Don't take a bug report's suggested fix at face value — verify the layer - Understand *why* neighbors do what they do before deviating
When the entrypoint loader is `.md`, bun now reads the file, renders it to ANSI, prints to stdout, and exits — no JavaScript VM spin-up. ### Supported - Headings (h1-h6) with colored underlines - **Bold**, *italic*, ~~strikethrough~~, underline, \`inline code\` - Ordered / unordered / task lists (with nesting) - Blockquotes (nested) - Horizontal rules - Fenced code blocks with syntax highlighting for JS/TS/JSX/TSX via \`QuickAndDirtyJavaScriptSyntaxHighlighter\` - Tables with per-column alignment + box-drawing borders. Column widths are computed globally so **CJK, emoji, and combining characters align correctly** (uses \`bun.strings.visible.width.exclude_ansi_colors.utf8\`) - OSC 8 hyperlinks (with \"text (url)\" fallback when stdout isn't a TTY) - Images as alt text with the src as an OSC 8 link - Wikilinks - Autolinks (url, www, email) - Word-wrapping respecting COLUMNS - Light / dark theme selection via \`COLORFGBG\`, \`NO_COLOR\`, \`FORCE_COLOR\` ### Wiring - \`.md\` added to \`default_loaders\` so \`bun ./file.md\` resolves it. - \`_bootAndHandleError\` short-circuits when the loader resolves to \`.md\`, calls the renderer, flushes, and exits. - No VM boot, no JSC init — much faster than spinning up a runtime. ### Tests \`test/cli/run/markdown-entrypoint.test.ts\` — 20 snapshot tests covering every feature above plus NO_COLOR mode and the \`.markdown\` extension. Tables specifically include CJK, emoji, and combining-character rows to verify multi-width grapheme alignment. --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: robobun <robobun@bun.sh> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
…_callback (oven-sh#29067) ## What Take an explicit \`us_socket_context_ref\` in \`us_socket_context_connect\` immediately after setting \`c->pending_resolve_callback = 1\` (before \`Bun__addrinfo_set\` exposes \`c\` to the DNS worker), and drop it at every exit of \`us_internal_socket_after_resolve\`. Snapshot \`context\`/\`ssl\` into locals at the top of \`after_resolve\` so the unref uses a stable pointer even after \`us_connecting_socket_free\`/\`close\` has unlinked \`c\`. ## Why v1.3.11 crash signature, linux x86_64_baseline: \`\`\` Segmentation fault at address 0x00000000 - loop.c:238: us_loop_run_bun_tick \`\`\` \`loop\` is the first field of \`us_socket_context_t\`, so address 0x0 means \`c->context\` was NULL when the inlined \`after_resolve\` ran \`c->context->loop->num_polls--\`. The existing invariant relies on the link-ref taken by \`us_internal_socket_context_link_connecting_socket\`; this PR makes the ref independent of link/unlink so the context is guaranteed live for the entire pending-resolve window regardless of which list \`c\` is on. Features in the report: spawn, fetch, http_client_proxy, WebSocket, abort_signal. \`Bun.spawnSync\` (whose tick changed in v1.3.11 from \`tickWithoutJS\` to \`tickTasksOnly\`) lets \`dns_ready_head\` accumulate while the JS thread is blocked, widening the window. ## Testing No deterministic repro on macOS — the production crash is on linux with the work-pool getaddrinfo path; an ASAN stress test (concurrent multi-IP WebSocket + spawnSync + abort, 400 iters) runs clean on the debug build. Existing suites pass on the debug build: socket.test.ts (29/29), node-net.test.ts (31/31+1 skip), resolve-dns.test.ts (71/71). ## Related - oven-sh#29064 snapshots \`loop\` before unref in \`us_connecting_socket_free\` (independent ordering fix) - oven-sh#29065 takes the ref inside \`after_resolve\` instead — this PR supersedes it by acquiring the ref before the DNS thread can observe \`c\`
… connecting socket is closed (oven-sh#29068) Stacked on oven-sh#29067. Two commits, both addressing the case where a \`us_connecting_socket_t\` is closed before \`us_internal_socket_after_resolve\` runs. ## 642c35c — release addrinfo_req in the closed branch of after_resolve When the socket is closed before the DNS callback fires, \`us_connecting_socket_close()\` finds \`c->addrinfo_req == NULL\` (it is only assigned by \`us_internal_dns_callback\`) and skips \`Bun__addrinfo_freeRequest\`. The callback later assigns it and enqueues \`c\` on \`dns_ready_head\`; \`after_resolve\` then takes the \`c->closed\` branch and frees \`c\` without ever decrementing the request refcount taken by \`Bun__addrinfo_get\`. Release it here. ## c311958 — cancel the pending DNS notify so close does not have to wait for getaddrinfo Previously, closing while DNS resolution was still in flight only marked \`c->closed\`; the socket and its context remained pinned until the DNS worker eventually called back. If \`getaddrinfo\` never returned, both leaked. \`c->addrinfo_req\` is now stored at connect time and a new \`Bun__addrinfo_cancel(req, socket)\` removes the socket from the request notify list under the global cache lock. \`afterResult\` sets \`result\` and moves the notify list out under that same lock, so a non-null \`result\` means the callback has fired or is about to fire and cancellation is refused. In \`us_connecting_socket_close\`, when \`pending_resolve_callback\` is set, attempt cancellation. On success: undo what \`us_socket_context_connect\` set up (\`num_polls\`/\`active_handles\`, the pending-resolve context ref from oven-sh#29067, the request refcount) and free the socket immediately. On failure: leave the socket alive so \`after_resolve\` finishes teardown via its closed branch (first commit). ## Testing socket.test.ts (29/29), node-net.test.ts (31/31+1 skip), resolve-dns.test.ts (71/71), fetch-abort (2/2), and a 100-iter abort smoke test all pass on the debug build. The leak itself is a pinned \`internal.Request\` refcount and a pinned context, neither observable from JS without instrumentation, so no fail-before regression test. --------- Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
…ven-sh#29062) Changes the Windows aarch64 runner sizes from `D16ps_v6`/`D4ps_v6` to `D16pds_v6`/`D4pds_v6`. Same vCPU/RAM; the `d` infix adds local NVMe, which lets robobun place the OS disk there via `diffDiskSettings` (already wired up on the robobun side) — faster boot, faster disk I/O, and no per-VM managed-disk cost. **Do not merge until the `StandardDpdsv6Family` vCPU quota (requested to 100 across all regions) is approved** — current limit is 10 cores/region, which is less than one `D16pds_v6`.
### What does this PR do? ### How did you verify your code works? --------- Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
kepae
force-pushed
the
install-progress-counter
branch
from
April 9, 2026 13:25
e7aceaa to
aab6c8e
Compare
Fixes oven-sh#29072 ## Repro ```js import os from 'node:os'; console.log('total:', os.totalmem()); console.log('free :', os.freemem()); console.log('ratio:', (os.freemem() / os.totalmem()).toFixed(4)); ``` On a Linux host with `MemTotal: 130G`, `MemFree: 36G`, `MemAvailable: 117G`: ``` $ bun repro.mjs $ node repro.mjs total: 133613760512 total: 133613760512 free : 36713017344 free : 121134342144 ratio: 0.2748 ratio: 0.9066 ``` ## Cause `src/bun.js/bindings/OsBinding.cpp` on Linux used `sysinfo(2)`: ```cpp struct sysinfo info; if (sysinfo(&info) == 0) { return info.freeram * info.mem_unit; } ``` `sysinfo.freeram` counts only pages that are completely unused — it matches `MemFree` in `/proc/meminfo` and **excludes** the reclaimable page cache. On a healthy Linux system the page cache is typically huge, so this is always much smaller than what applications expect. Node.js (via libuv, `vendor/libuv/src/unix/linux.c:2074`) reads `MemAvailable:` from `/proc/meminfo` and only falls back to `sysinfo.freeram` on failure. `MemAvailable` is the kernel's own estimate of memory available for starting new applications without swapping — it includes reclaimable cache/slab, which is what `os.freemem()` is documented to return. ## Fix Port libuv's `uv__read_proc_meminfo` + `uv_get_free_memory` logic to `Bun__Os__getFreeMemory`: 1. Read `MemAvailable:` from `/proc/meminfo` 2. Fall back to `sysinfo.freeram * mem_unit` only if that fails Parsing matches libuv's implementation (sscanf `"%PRIu64 kB"`, 4096-byte buffer). Darwin and Windows paths are unchanged. ## Test `test/regression/issue/29072.test.ts` — reads `/proc/meminfo` directly and asserts that `os.freemem()` is close to `MemAvailable` (not `MemFree`). On any system with a populated page cache the two differ enough that the pre-fix behaviour fails the tolerance check, and when `MemAvailable` is meaningfully larger than `MemFree` the test also asserts that `freemem()` is closer to `MemAvailable` than to `MemFree`. Skipped on non-Linux (Darwin/Windows don't use this code path). --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
) Bumps `ZIG_COMMIT_PARALLEL` to `7d3c0c9b36` (oven-sh/zig `upgrade-0.15.2` tip) and drops the darwin-only gate so Linux local builds use the parallel compiler too. The previous parallel commit hung on Linux during ELF flush — sharded codegen emits per-function COMDAT sections and zig's self-hosted ELF `-r` merge did one `pread`+alloc per section (hundreds of thousands of tiny syscalls). `7d3c0c9b36` batches those into per-file contiguous reads. CI and Windows stay on the stable compiler. > [!NOTE] > Don't merge until autobuild for `7d3c0c9b36` finishes on oven-sh/zig (compiler download will 404 otherwise).
Adds a short section to CLAUDE.md clarifying that edits to `packages/bun-types/**/*.d.ts` are type-only and don't need a native build — `bun-types.test.ts` just packs the declarations and runs `tsc` against fixtures, so it can be run with system Bun directly. Prevents agents from kicking off a 30-min cold build to validate a `.d.ts` tweak.
Bump `LATEST` to 1.3.12 and `package.json` version to 1.3.13 following the v1.3.12 release.
…ven-sh#29101) ## What does this PR do? Calling `ws.close()` on a `wss://` WebSocket that is connecting through an HTTP CONNECT proxy while the inner TLS handshake is in flight double-freed `WebSocketProxy.#target_host`, corrupting the mimalloc freelist. Subsequent allocations (X509 parse, `path.normalize`, `Buffer.from`) crashed at garbage addresses. The re-entrancy chain: `clearData()` → `WebSocketProxy.deinit()` frees `target_host` then calls `tunnel.shutdown()` → `SSLWrapper.shutdown(true)` synchronously fires `onHandshake`/`onClose` → `WebSocketProxyTunnel.onClose` → `upgrade_client.terminate()` → `fail()` → `tcp.close()` → `handleClose()` → `clearData()` again, before `this.proxy` had been set to `null`, so `deinit()` runs a second time. Fix: null `this.proxy` and detach the tunnel's non-owning `#upgrade_client` back-pointer before calling `deinit()`, so the SSLWrapper shutdown callbacks cannot re-enter the upgrade client. `#upgrade_client` is not an owning reference (the tunnel's own `deinit` never frees it and `setConnectedWebSocket` already clears it the same way), so detaching does not leak. ## Related issues Most likely fixes oven-sh#28153 — crash report shows `WebSocket(25)` + `http_client_proxy(966)` both active, segfault at `0x73746E657665227B` = ASCII `{"events`, the exact freed-string-as-pointer freelist-corruption signature this bug produces. Most likely fixes oven-sh#27790 — same reporter/app/env as oven-sh#28153 with `WebSocket(4)` + `http_client_proxy(84)`, segfault after ~3min uptime. Not a duplicate of oven-sh#28965 — that PR fixes a memory leak (tunnel.shutdown never closed the underlying socket); this one fixes a double-free. They touch adjacent code but are independent bugs. ## How did you verify your code works? New test `test/js/web/websocket/websocket-proxy-close-reentrancy.test.ts` spawns a fixture that opens 80 `wss://` connections through a local HTTP CONNECT proxy and calls `close()` at staggered offsets across the CONNECT → TLS handshake window. Before this change the fixture segfaults in ~50ms on a release build (crash address `0x5448202F20544547` = ASCII `"GET / HT"` — freed `target_host` bytes reinterpreted as a pointer). With the fix it exits 0 in ~1.3s on a debug build. Skipped on Windows where libuv defers the close callback so the re-entrancy cannot occur.
Pass `--locked` to `cargo build` in `emitCargo()` so transitive Rust crate versions can't silently re-resolve against crates.io. Bumps `LOLHTML_COMMIT` one commit forward (`e3aa547`→`77127cd`) because the old pin's `c-api/Cargo.lock` was stale (upstream forgot to regenerate it for 2.7.2). The new pin is the upstream lockfile fix; only `c-api/Cargo.lock` differs between the two — same lol-html 2.7.2 source.
…cket_free (oven-sh#29064) us_internal_socket_context_unlink_connecting_socket() calls us_socket_context_unref(), which can drop the last reference and queue the context for free. The two lines that follow then dereference c->context->loop. Snapshot the loop pointer before unlink runs. Matches the existing snapshot idiom in start_connections (context.c:596-597). Part of defensive hardening for the v1.3.11 crash signature (segfault at 0x0 inside us_loop_run_bun_tick at loop.c:238, the inlined us_internal_socket_after_resolve / DNS drain path). No deterministic repro; ASAN stress test (WebSocket + cancel + spawnSync, 50 iters) runs clean on the debug build. Existing socket / node:net / DNS / proxy tests pass (proxy redirect timeouts are pre-existing on main). Companion PR: ref/unref the context across us_internal_socket_after_resolve.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
This PR arranges the installed packages counter to the left of the package names rather than the right. This keeps the counter more readable -- its easier to tolerate 1-3 character shifts sequentially rather than more variable package names flying by.
This PR also attempts to make the buf writer conditionals more readable in passing. The ellipsis behavior remains the same, printed only for nodes without counters.
Before:
After:
How did you verify your code works?
Created test project with
package.json:{"dependencies":{"next":"latest", "gatsby": "latest"}}and ran:
verified that the buffer displayed the counter and ellipses as expected.