You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 75bd87c
Browse filesBrowse the repository at this point in the historyBrowse files
fix(ci): unblock Windows CI — force LF checkout, then fix the Windows bugs it revealed (#286)
Windows CI has been red for months. This fixes the gate that hid the
problem, then fixes everything the gate was hiding.
## 1. Why Windows CI was red
`test (windows-latest)` failed `vp check` with ~1028 files reported as
mis-formatted — effectively the whole repo, on the last 5+ consecutive
`main` runs.
The repo had **no `.gitattributes`**. On Windows, git's default
`core.autocrlf=true` converts LF→CRLF on checkout, while oxfmt enforces
LF. Ubuntu keeps LF and passed, so the failure was platform-only.
Fix: a root `.gitattributes` pinning text files to LF everywhere (`*
text=auto eol=lf`) plus explicit `binary` markers. Checkout-only — every
committed blob is already LF, so `git add --renormalize .` rewrites
nothing.
## 2. What that revealed
With the formatting gate passing, the Windows job reached the test suite
**for the first time** and 42 tests failed. `provision.ts` /
`cloudflare-deploy.ts` landed in #273 while Windows CI was already dying
at formatting, so this code had *never executed on Windows*.
### Product bugs — `packages/void/src/cli/provision.ts`
All changes are `win32`-gated; POSIX behavior is byte-identical.
- **`canonicalizeConfigPath` returned two identities for one file.**
Every path in the module comes from `pathe` (POSIX-normalized), but
`realpathSync` is `node:fs` and returns a **native** path. An existing
config canonicalized to `D:\a\…`, a not-yet-created one to `D:/a/…` —
splitting the lock identity, the wrangler `-c` argv, and the paths
printed in errors, defeating the "ONE identity" contract the function
exists to provide.
- **The quarantine gate could fail OPEN.** Its probe target was `-pgid`
with no platform guard, but `process.kill(-n, 0)` on win32 resolves to
`OpenProcess((DWORD)-n)`, which fails and surfaces as `ESRCH` —
reporting any recorded owner "confirmed dead" and clearing a **live**
quarantine. The identical probe is already guarded in the retention
watchdog; this mirrors it.
- **An already-exited child could be quarantined.** Windows bypassed the
`killProcessGroup` seam for `child.kill()`, whose `false` conflates
`ESRCH` with `EPERM`, and `exitCode`/`signalCode` are set asynchronously
so a just-exited child still reads as running. Windows now routes
through the same seam, where `process.kill` throws `ESRCH` and is
classified "already gone".
- **Termination confirmation was assumed, not earned.** `probeGroupGone`
returned a constant `true` on Windows, degrading "never release the lock
until the child is confirmed dead" to a single fact. It now performs a
real `kill(pid, 0)` liveness probe; descendants stay covered by libuv's
global Job Object.
### Test-only fixes (product verified correct)
- **env-schema probe (10 failures).** The fixtures spliced an absolute
path in as an **ESM specifier**; on Windows `D:/…` parses as protocol
`d:` and Node rejects it with `ERR_UNSUPPORTED_ESM_URL_SCHEME`, so the
child never evaluated and every result came back `schema-error`. Now
built with `pathToFileURL`, as the product does. Real users are
unaffected — their `env.ts` uses bare specifiers and the product already
uses `pathToFileURL`.
- **Deploy pointer.** `@cloudflare/vite-plugin` writes it with
`node:path.relative` (native separators); the assertion pinned a POSIX
literal. Compare the normalized form.
- **pgid assertion.** `writeQuarantineSidecar` records `null` on win32
by design.
- **Signal-delivery cases skipped on Windows**, which cannot deliver a
signal to another process (libuv maps SIGTERM/SIGINT/SIGKILL to
`TerminateProcess`). This includes one case that "passed" there only
because the kernel Job Object reaped the child — a green that proved
nothing.
Two failures (`[33]`/`[34]`) are fixed by the **product** change, not by
touching them: their fixtures write a positive `pgid` against a live
process and demand the gate fail closed. The tests were right; the code
was wrong. They now guard the fix.
## Verification
- `packages/void` full suite on POSIX: **3673 pass**, 0 fail
- typecheck clean, `vp check` clean
- POSIX unchanged — every product change is `isWindows`-gated
- Windows verified by CI on this PR via the `test: windows` label
### Windows progression
| Run | Windows result |
|---|---|
| baseline (before this PR) | `vp check` died on ~1028 files — suite
never ran |
| after `.gitattributes` | gate passes; **42** test failures surfaced |
| after the provision/probe fixes | **2** failures (both teardown-only;
all 40 behavioural failures cleared) |
| after the teardown fix | pending |
The 40 cleared include all 21 path-separator, all 10 env-probe, the
build pointer, the pgid/quarantine set, and the signal-delivery cases.
Two of them (`[33]`/`[34]`) were fixed by the **product** change rather
than by touching the test: their fixtures write a positive `pgid`
against a live process and require the gate to fail closed. The tests
were right; the code was wrong. They now guard the fix.
## Known remaining
- **Teardown EPERM** in the kill-classify block: every child there runs
with `cwd` = the temp dir, and Windows will not remove a directory that
is a live process's cwd. Teardown now reaps any recorded pid before
removing and, if it still fails, reports which children were alive and
what entries remain rather than a bare errno.
- **Browser-test flakiness on Windows**: one metaframework browser case
has failed per run, but a *different* one each time (Solid
`net::ERR_NO_BUFFER_SPACE`, then SvelteKit `__spa_marker` undefined).
That pattern points at runner resource contention rather than a code
defect, so it is being tracked across runs instead of patched
speculatively.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_019wnwcxjavdb2xys8XGZora
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes touch provision locking and quarantine gates on Windows
(fail-closed correctness); POSIX paths are explicitly preserved behind
platform checks.
>
> **Overview**
> **Unblocks Windows CI** by adding root `.gitattributes` (`eol=lf`) so
checkout on `windows-latest` matches oxfmt’s LF expectation, then fixes
the Windows-only failures that surfaced once `vp check` passed.
>
> **`provision.ts` (win32-gated):** Normalizes `realpathSync` results to
POSIX so lock/wrangler paths stay one identity; fixes quarantine probing
so negative PIDs are never used on Windows (fail-closed); routes child
termination through `killProcessGroup` / `kill(pid, 0)` instead of
assuming the child is gone.
>
> **Tests & harness:** Env-schema fixtures use `pathToFileURL`; deploy
pointer assertions normalize separators; POSIX-only signal tests skip on
Windows; kill-classify temps are per-test with safer Windows teardown;
Vitest splits **shared-playground** integration into a serialized
project and forwards live-e2e env there.
>
> **CI:** Optional Defender exclusions for workspace/temp on trusted
Windows runs; `.gitignore` for `**/.tmp-*`.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1daa7f8. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments