# Conflicts: # .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml # .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md # .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md # vendor/README.md
8.6 KiB
Agent Note: Dual Wine and native Windows pull-request CI
Status: implemented
English | 中文
Problem
The required pull-request Windows verdict needs a fast win32 toolchain signal without making the aggregate wait for scarce Windows capacity. Wine provides that critical-path signal but runs over a Linux kernel and case-sensitive ext4, uses a hoisted dependency layout, and cannot prove NTFS, DACL, ConPTY, crash durability, or native process behavior. With the native serial references disabled, every pull-request head also needs an automatic real Windows-kernel result.
A coverage audit found that stale branch state had restored temporary exclusions for supported LSP sources. Native Windows therefore needed to execute the complete supported source inventory at the same 100%-per-file threshold instead of relying on a smaller platform-specific denominator.
Decision
The required windows job in ci.yml remains windows node 24 / wine blocking on ubuntu-latest. It retains the checksum-verified Windows Node, Wine apt and pnpm caches, a hoisted install confined to a workspace snapshot, and the shared Wine gate script that runs the workspace build and production site. The stable windows job id remains a dependency of all checks passed. The archived Wine experiment preserves its measured trade-offs, while this note owns the current dual topology.
Every pull request also starts an ordinary independent windows-native job named windows node 24 / native complete on GitHub's standard two-core windows-2025 image. It enables Developer Mode for workspace symlinks, provisions the repository-pinned pnpm through pnpm/action-setup, performs an immutable install without a transferred store archive, and runs pnpm run check:ci:windows-complete under native PowerShell. A 60-minute timeout bounds a stuck gate without treating the measured performance target as a correctness deadline.
The native job is deliberately absent from all-checks-passed.needs and does not use continue-on-error: the aggregate neither waits for it nor changes conclusion because of it, while the job retains its own unmasked result. Workspace build, production-site, and 100%-per-file coverage failures make the native job fail. The broader static, documentation, package, and built-artifact portability inventory remains observational. Linux remains the owner of duplicate lint and snapshot enforcement, while native Windows independently enforces supported-source coverage.
The standard native lane gives coverage and the top-level gate scheduler one worker each so the instrumented and exempt-heavy suites do not overlap. Process-global, real-process-tree, and timing-sensitive suites run in the fork-isolated Vitest project but still merge coverage into the same per-file threshold. The LSP sources remain in the denominator; only intrinsically peer-platform source arms use narrow annotated V8 ignores, with their behavior tests retained on the owning platform.
Portable filesystem fixtures derive paths with node:path, compare native realpath identities, preserve file URLs at Node launcher boundaries, normalize only API-owned separators or line endings, and use filenames legal on every host. POSIX-only signal, mode-bit, unreadability, and writer-lock cases are platform-gated; portable failure contracts instead assert structured error codes, rollback, last-good state, atomic replacement, and absence of temporary residue through conflicts available on every host. Stress and integration workloads keep their original assertions and receive explicit bounded time budgets where Windows instrumentation or process teardown can exceed Vitest's default ceiling.
Native watchers use canonicalizeWatchPath() to realpath the deepest existing ancestor, prove it is an enumerable directory when a suffix is missing, and restore that suffix. This prevents Windows 8.3 aliases from being mixed with long-form libuv events and preserves ENOTDIR for a regular-file ancestor on every host. Settings, credentials, skill roots, and Cordis HMR retain configured paths for discovery and diagnostics; module HMR uses the canonical spelling for Node's load-cache identity, attaches listeners, and awaits its main watcher before plugin startup settles, so an immediate post-boot edit cannot race the initial scan. A skill root that is itself a symbolic link remains unexpanded when watchFollowSymlinks: false, allowing Chokidar to enforce that boundary.
Windows durable JSONL paths keep drive roots in native spelling and apply the extended-length namespace only to descendants and staging paths. The ACP teardown ladder uses real Node children, proves graceful and forced tiers with host-appropriate outcomes, and avoids claiming POSIX signal delivery on Windows. Executable fixtures provide .cmd shims and PATHEXT where the product accepts a bare command. Repository-cache helpers live inside the selected Git subpath so their declared file: dependencies expose command shims identically on Windows. The bundled installer exports pnpm's own workspace-ignore configuration so a nested Git-package install cannot rejoin an enclosing workspace when Windows lifecycle setup takes precedence over the transaction-owned command wrapper.
Post-boot profile watcher setup proceeds only while the root fiber and Loader are both live. A concurrent setup error is contained only when the same invocation's recorded signal already owns shutdown; unrelated HMR failures remain loud. The vendored Include serializes debounced writes, retries only transient access or busy failures with bounded backoff, and observes every timer rejection. A terminal persistence failure remains on the queue and is rethrown to the teardown owner, while successful teardown drains the latest write.
Shiki disables lazy TextMate-regex compilation and warms each boot grammar before user content enters the unchanged per-line tokenization budget, so scheduler contention cannot publish a partial highlighted stream. The Codex real-product fixture is pinned to stable 0.147.0 schemas and selects an actually advertised command tool and argument shape, preserving the provider-owned protocol while proving unattended rejection and whole-tree exit on each host.
Alternatives considered
Make native Windows a dependency of all checks passed. This gives the aggregate the highest-fidelity Windows verdict, but makes every merge wait for the slowest hosted job and for Windows capacity. The independent result keeps the signal automatic without changing the existing required path.
Run only Wine on pull requests. Wine reaches blocking win32 toolchain branches quickly, but can report green while a real NT, NTFS, PowerShell, process, or addon contract is broken.
Mark the native job continue-on-error. That would make its check appear successful after a gate failure. Keeping an ordinary independent job preserves the diagnostic conclusion; omission from aggregate needs is the only non-blocking mechanism.
Exclude unsupported-looking files or weaken Windows fixtures. Rejected because the affected LSP, watcher, persistence, client, and process behavior is supported. Peer-platform branches are marked narrowly; portable outcomes stay in the denominator and are exercised through host-realistic fixtures.
Use an organization-owned larger Windows runner. Larger images can reduce wall time, but the portable diagnostic path would then depend on repository-external labels and allocation. Standard windows-2025 remains the baseline until an exact-head benchmark proves a stable configuration worth that dependency.
Consequences
Wine preserves the required aggregate's existing critical path and job identity. Native Windows can still be pending or red when all checks passed turns green, so branch protection consumes Wine while reviewers and follow-up automation consume the separate native result.
Every pull request nevertheless receives a real NT kernel, NTFS, PowerShell, Windows process, native addon, and supported-source coverage signal. The native job duplicates setup and the two blocking builds and is materially slower on the standard image, but it also exposes path, watcher, lifecycle, and fixture defects hidden by the compatibility lane.
Maintainers must preserve two intentional execution topologies: the Wine snapshot uses Linux installation plus a hoisted layout to reach win32 binaries, while the native job uses the immutable workspace on Windows. A failure unique to either job must be classified against that boundary rather than weakened or silently skipped.