Skip to content

test(hot): skip transient file-access errors from Windows rm+rename gap - #34043

Merged
dylan-conway merged 3 commits into
mainfrom
farm/16887af3/hot-test-windows-rm-rename-gap
Jul 13, 2026
Merged

test(hot): skip transient file-access errors from Windows rm+rename gap#34043
dylan-conway merged 3 commits into
mainfrom
farm/16887af3/hot-test-windows-rm-rename-gap

Conversation

@robobun

@robobun robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes test/cli/hot/hot.test.ts going red on Windows lanes (build 72241, Windows 11 aarch64).

Repro

Expected to contain: "error: 2"
Received: "error: Module not found 'C:\\Windows\\Temp\\...\\hot-runner-root.js'"
    at driveErrorReloadCycle (test/cli/hot/hot.test.ts:70:20)
(fail) should not remap against a stale sourcemap after a partial-file reload

Reproduces locally on both Windows x64 and aarch64: 21/50 fail rate on x64, similar on aarch64.

Cause

writeHotFileAtomicSync on Windows does rmSync(path) then renameSync(tmp, path) (added in #30412), because renameSync over an existing file EPERMs while --hot has the destination open for reading. Between the rm and the rename the entry file briefly does not exist. A --hot reload that lands in that gap prints one of:

  • error: Module not found '<path>' (resolver)
  • error: ENOENT reading "<path>" (transpiler read)
  • error: EPERM reading "<path>" (transpiler read, delete pending)

driveErrorReloadCycle matched these at line.includes("error:") and then asserted they contain error: ${reloadCounter}, which fails.

The "stale sourcemap" test hits this much more often than the sibling "sourcemap generation" test because its hot file self-writes __filename on every run, so there is almost always a reload in flight when the test's next writeHotFileAtomicSync runs.

Fix

driveErrorReloadCycle now skips these transient file-access error lines and keeps reading. The rename's own watcher event drives the real reload, so re-saving from the harness on these lines would only race with it (an earlier revision that did re-save occasionally produced stale-sourcemap coords on x64 from exactly that race).

Verification

before after
windows-x64 (stale-sourcemap test) 29/50 pass 80/80 pass
windows-aarch64 (stale-sourcemap test) repro'd same as CI 80/80 pass
full hot.test.ts suite, both Windows flaky 15/15 pass each
linux bun bd test hot.test.ts 12/12 12/12

no test proof · iteration 1 · Platform-specific test-only change; deferring to CI.

On Windows, writeHotFileAtomicSync does rmSync(path) before renameSync(tmp, path)
because renameSync EPERMs when --hot has the destination open. Between the rm
and the rename, the entry file briefly does not exist; a --hot reload that lands
in that window prints 'Module not found', 'ENOENT reading', or 'EPERM reading'
(delete pending) instead of the expected 'error: N'.

driveErrorReloadCycle now skips those lines and keeps reading. The rename's own
watcher event drives the real reload, so re-saving on these lines would only
race with it.

Before: 29/50 pass on windows-x64, similar on windows-aarch64
After:  80/80 on both, full suite 15/15 on both, linux debug build unchanged
@robobun robobun mentioned this pull request Jul 12, 2026
@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:26 PM PT - Jul 13th, 2026

@robobun, your commit d8df928 is building: #72495

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 676d461e-a5e0-46af-b17e-b365845daf17

📥 Commits

Reviewing files that changed from the base of the PR and between 2e2230a and d8df928.

📒 Files selected for processing (1)
  • test/cli/hot/hot.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 — targeted test-only de-flake for a Windows-specific race in the hot-reload harness.

What was reviewed:

  • Checked the new skip regex /Module not found|\w+ reading "/ cannot match the real error: <N> lines the test asserts on, so no false passes — a genuine module-not-found regression would still time out rather than pass.
  • Verified placement: the skip runs after the error: filter and before the stale-counter/assertion branches, so triggered and buffer state stay consistent when a transient line is skipped.
  • Confirmed no production code touched; the added comment on writeHotFileAtomicSync just cross-references the skip.
Extended reasoning...

Overview

Single-file test change to test/cli/hot/hot.test.ts. Adds a one-line regex guard inside driveErrorReloadCycle that skips transient Module not found / ENOENT reading / EPERM reading error lines that --hot can print on Windows when a reload lands in the gap between writeHotFileAtomicSync's rmSync and renameSync. Also expands two comments to document the cross-reference between the helper and the skip.

Security risks

None. Test-harness-only change; no production code, no external inputs.

Level of scrutiny

Low. This is a de-flake of a known-flaky Windows CI test. The PR description traces the root cause (rm+rename gap introduced in #30412), explains why the fix is a skip rather than a re-save (re-saving raced the rename's own watcher event and produced stale-sourcemap coords), and includes 80/80-run verification on both Windows arches plus a Linux no-regression check.

Other factors

  • The regex is narrow enough: real assertion targets are error: <number> which cannot contain Module not found or \w+ reading ". If the pattern ever fired on every reload (a real regression), reloadCounter would never advance and the test would time out — it can't silently pass.
  • Loop state after continue is correct: triggered stays as-is, so a chunk containing only a transient error triggers no onReload, matching the PR's stated intent that the rename's own watcher event drives the next reload.
  • The 5-line comment technically exceeds CLAUDE.md's 3-line guideline, but this file already has several longer race-condition explainer comments in the same style; verifiers ruled this out as a non-issue.
  • No prior reviews, no outstanding comments, no CODEOWNER on this test file.

@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 test-harness de-flake for a Windows-only race.

What was reviewed:

  • Regex /Module not found|\w+ reading "/ cannot match the expected error: <N> lines, so no false-positive skips.
  • Placement (after the target-count check, before the stale-counter branch and assertion) means remaining buffered lines are still processed via continue.
  • The early buffer gate /error: .*[0-9]\n/ interacts fine — either the transient line buffers until a numeric error arrives, or it is skipped when lines are split.
Extended reasoning...

Overview

Test-only change to test/cli/hot/hot.test.ts. Adds a one-line regex skip in the shared driveErrorReloadCycle helper so transient Module not found / ENOENT reading "…" / EPERM reading "…" lines emitted by --hot during the Windows rmSync+renameSync gap in writeHotFileAtomicSync are ignored instead of failing the expect(line).toContain("error: N") assertion. Also expands the existing comment on writeHotFileAtomicSync to cross-reference the skip. No production code touched.

Security risks

None. Pure test-harness change; no runtime, auth, crypto, or user-facing code paths involved.

Level of scrutiny

Low. This is a targeted CI flake fix in a test helper, addressing a race that the same file's writeHotFileAtomicSync (added in #30412) itself introduced on Windows. The mechanism is clearly explained in both the PR description and the inline comment, and the verification table shows 80/80 pass on both Windows arches vs ~29/50 before. The regex is narrow enough that it cannot accidentally match the numeric error: <counter> lines the tests actually assert on, and \w+ reading " is specific to Bun's transpiler read-error format.

Other factors

  • The continue preserves the rest of the buffered lines in the current chunk, so a real numeric error following the transient one in the same chunk is still processed.
  • The choice to skip (not re-save) is deliberate and explained: the rename's own watcher event already drives the next reload, and re-saving would race it — the PR notes an earlier revision that re-saved produced stale-sourcemap coords.
  • No prior human or bot review comments to address; bug-hunting system found nothing.

@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status: test/cli/hot/hot.test.ts passed on every lane of both CI runs (72258, 72266), including Windows 11 aarch64 where it was red in build 72241.

The remaining red lanes are unrelated pre-existing flakes that this diff cannot touch (only test/cli/hot/hot.test.ts is modified):

  • test/napi/napi.test.ts napi_wrap > has the right lifetime GC timing, Windows x64/x64-baseline (also red in the reporting build 72241)
  • test/js/web/fetch/fetch-backpressure.test.ts, Windows x64-baseline
  • test/js/node/test/parallel/test-fs-promises-file-handle-readFile.js, linux x64-asan
  • test/js/node/net/net-mongodb-pattern-leak.test.ts, linux x64-baseline / ubuntu aarch64
  • test/js/bun/s3/s3.test.ts, linux aarch64

Ready for review/merge.

POSIX rename is atomic, so 'Module not found' / 'ENOENT reading' / 'EPERM
reading' on the entry during these tests would be a real bug there and should
fail loudly.
@dylan-conway
dylan-conway merged commit 7d44148 into main Jul 13, 2026
3 of 5 checks passed
@dylan-conway
dylan-conway deleted the farm/16887af3/hot-test-windows-rm-rename-gap branch July 13, 2026 20:26

@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 — test-only Windows flake fix, gated so POSIX still fails loud on the same errors.

What was reviewed:

  • Skip is placed after the error: filter and before both the stale-counter re-save and the expect(...toContain) assertion, so transient lines are neither re-saved (avoiding the race the PR notes) nor asserted on.
  • Regex \w+ reading " is broad but only reachable on Windows inside driveErrorReloadCycle, where all file writes are harness-driven — any such line is the rm+rename gap by construction.
  • isWindows export exists in test/harness.ts; other tests in the file (including "preload not found") don't route through this helper and are unaffected.
Extended reasoning...

Overview

Modifies only test/cli/hot/hot.test.ts: imports isWindows from harness, adds a Windows-gated continue in driveErrorReloadCycle for stderr lines matching Module not found / \w+ reading ", and expands the existing comment on writeHotFileAtomicSync to cross-reference the skip. No runtime code is touched.

Security risks

None. Test harness change; no auth, crypto, network, or user-facing input handling.

Level of scrutiny

Low. This is a targeted de-flake of a known Windows CI failure, confined to a single test helper. The change is additive (one gated continue) and cannot alter behavior on non-Windows platforms. The mechanism is well-explained: writeHotFileAtomicSync on Windows must rmSync before renameSync (from #30412), leaving a brief window where a concurrent --hot reload sees the file missing and prints a resolver/transpiler error that the test previously asserted against as if it were the expected error: N line.

Other factors

  • Placement is correct: the skip sits after the line.includes("error:") gate and the reloadCounter >= targetCount check, but before the stale-counter re-save branch and the expect(line).toContain(error: ${reloadCounter}) assertion. Choosing continue (keep reading) over re-saving is deliberate — the rename's own watcher event drives the real reload, and the PR description notes an earlier re-saving revision introduced its own race.
  • The isWindows gate preserves signal on POSIX, where rename(2) is atomic and these errors would indicate a real regression — consistent with the repo's "never silently weaken an existing test" guidance.
  • The regex is intentionally loose (\w+ reading " covers ENOENT/EPERM/etc.), but it only runs on lines already containing error: in a helper whose sole callers write the entry file themselves via writeHotFileAtomicSync, so there's no plausible non-transient match to mask.
  • CI confirms the fix: hot.test.ts passed on all lanes across two builds including the previously-red Windows 11 aarch64; remaining red lanes are unrelated files this diff cannot affect. The commit history shows the gate was tightened from all-platforms to Windows-only in a follow-up commit, which is the right call.

Jarred-Sumner pushed a commit that referenced this pull request Jul 30, 2026
…34166)

`test/regression/issue/20144/20144.test.ts` has been flaking on the
macOS aarch64 lanes (102 of the last 500 branch builds carry it in their
annotations) and went fully red on darwin 14 aarch64 in build
[72915](https://buildkite.com/bun/bun/builds/72915):

```
AssertionError: Expected values to be strictly equal:
+ 'SIGKILL'
- 'SIGINT'
✗ should not time out [1010.48ms]
```

### Cause

The test spawns a child that runs `bun:ffi`'s `cc()` and then sends an
IPC `"hej"` before entering `while (true)`. The parent replies with
`SIGINT`, and the assertion checks the child exited via `SIGINT`. A
`spawn` option of `timeout: 1000, killSignal: "SIGKILL"` is the hang
guard.

That 1s budget is spent almost entirely on child startup and the `cc()`
compile, not on the property being tested. On an idle macOS box the
round-trip is ~30 ms; on a local debug+ASAN build it is ~1.7 s, and
under concurrent test-runner load on the macOS tart VM runners it
crosses 1 s in release too, so `node:child_process` fires `SIGKILL`
before `"hej"` ever arrives.

There is no `src/` culprit in the window where the rate jumped: the
first heavily affected build (72519) and the adjacent clean one (72517)
share the same merge-base (`7d44148cfa`), and the only main commits
between the surrounding bases are test-only (#34043, #34081). This is a
latent tight timeout that CI load pushed over the edge.

### Fix

* Raise the kill switch to `20_000` ms to match the other hang-guard
tests in the repo (e.g.
`test/js/bun/css/angle-serialization-hang.test.ts`). The regression from
#20144 was a permanent hang, so the guard still fires on a real
regression; it just stops racing startup.
* Spawn with `bunExe()` / `bunEnv` so the child runs with the normalized
test environment instead of inheriting the CI runner's full env.
* Replace the `done` callback with `Promise.withResolvers`, wire
`child.on("error")` to reject, and assert the exit shape with
`expect(...).toEqual({ code: null, signal: "SIGINT" })` plus a check
that the ready message was actually received.

### Verification

```
$ bun bd test test/regression/issue/20144/20144.test.ts
(pass) should not time out [1720.56ms]
```

(Three consecutive passes on debug+ASAN; three on release. The debug
duration alone shows the old 1 s guard was never safe.)

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 0 · Platform-specific test-only change;
deferring to CI.

<!-- robobun:evidence:end -->
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