fix(cli): stabilize Windows CI tests and rebalance slow shards - #12723
Merged
marius-kilocode merged 3 commits intoJul 31, 2026
Conversation
Three Windows-only instabilities in the CLI unit suite: 1. httpapi-instance-route-auth.test.ts failed with an uncaught "Invalid handle" error. The test's ConfigProvider.layer( fromUnknown(...)) replaced the ambient config provider, blinding KILO_EXPERIMENTAL_DISABLE_FILEWATCHER=true that CI/preload sets. With the flag hidden, the @parcel/watcher Windows backend subscribed on the temp repo's .git; the tmpdir fixture then deleted that directory while the never-disposed per-test runtime still held the subscription, and CreateFileW failed with the hardcoded "Invalid handle" (napi rejection with no JS stack). Add the disable-filewatcher flag to every test config map that boots instances via the HttpApi app (instance-route-auth, cors, ui, exercise backend, kilo-edit, memory). 2. config-overlay.test.ts intermittently returned HTTP 500 on Windows. Filesystem.write's atomic temp-file+rename had no retry for Windows transient locked-file errors (EPERM/EACCES/EBUSY) from Defender/indexer and the detached background plugin install racing the rename in the same tmpdir. Mirror the proven cleanup.ts locked-error retry pattern with a short backoff, Windows-only. 3. Windows shards were badly imbalanced: the sharder weighted files by byte size, which concentrated every slow spawn/FS/lock-heavy file (snapshot, prompt, provider, run-process, instance-bootstrap, httpapi-session) into one shard (~612s vs ~356s siblings), and the resulting contention forced whole-file retries that doubled cost. Add TestShard.timedWeight and a committed test-timings.json seeded from CI junit data so shards balance by measured runtime (spread collapses from ~200s to ~18s) and contention-prone files spread across shards. Platforms without manifest entries fall back to size weighting.
Contributor
Code Review SummaryStatus: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (3 files this pass)Incremental review of
Notes and assumptions
Fix these issues in Kilo Cloud Previous Review Summaries (2 snapshots, latest commit 25affff)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 25affff)Status: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (1 file this pass)Incremental review of
Notes and assumptions
Fix these issues in Kilo Cloud Previous review (commit 06c1078)Status: 5 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (11 files)
Notes and assumptions
Reviewed by claude-opus-5 · Input: 32 · Output: 8.3K · Cached: 703.6K Review guidance: REVIEW.md from base branch |
Bun.file().size returns 0 (never throws) for missing paths, so the try/catch in timedWeight was dead code and stale/renamed manifest entries added their time to the scale numerator with zero size, inflating the size-to-time ratio that estimates unknown files. Skip entries with a non-positive on-disk size instead of catching a throw that never happens.
marius-kilocode
enabled auto-merge (squash)
July 31, 2026 10:05
marius-kilocode
disabled auto-merge
July 31, 2026 10:08
The committed test-timings.json (482 entries) was a maintenance burden: it goes stale as tests are added/renamed and no size-based heuristic can replace it (slow subprocess outliers like run-process.test.ts are 7kb but 112s, 10x the runtime-per-byte of other files). Revert the timing-weighted sharding to the prior size-based LPT. The Windows reliability fixes (ConfigProvider filewatcher flag + Filesystem.write locked-file retry) remain and are what eliminate the failures and the ~360s of retry overhead that dominated the 12m50s shard. A maintainable runtime-based rebalance (self-updating CI cache fed from the junit artifacts CI already uploads) is a separate follow-up.
marius-kilocode
enabled auto-merge (squash)
July 31, 2026 10:35
chrarnoldus
approved these changes
Jul 31, 2026
marius-kilocode
deleted the
investigate-window-test-performance-and-instabilit
branch
July 31, 2026 11:27
t7tran
pushed a commit
to t7tran/kilocode
that referenced
this pull request
Aug 14, 2026
…Org#12723) * fix(cli): stabilize Windows CI tests and rebalance slow shards Three Windows-only instabilities in the CLI unit suite: 1. httpapi-instance-route-auth.test.ts failed with an uncaught "Invalid handle" error. The test's ConfigProvider.layer( fromUnknown(...)) replaced the ambient config provider, blinding KILO_EXPERIMENTAL_DISABLE_FILEWATCHER=true that CI/preload sets. With the flag hidden, the @parcel/watcher Windows backend subscribed on the temp repo's .git; the tmpdir fixture then deleted that directory while the never-disposed per-test runtime still held the subscription, and CreateFileW failed with the hardcoded "Invalid handle" (napi rejection with no JS stack). Add the disable-filewatcher flag to every test config map that boots instances via the HttpApi app (instance-route-auth, cors, ui, exercise backend, kilo-edit, memory). 2. config-overlay.test.ts intermittently returned HTTP 500 on Windows. Filesystem.write's atomic temp-file+rename had no retry for Windows transient locked-file errors (EPERM/EACCES/EBUSY) from Defender/indexer and the detached background plugin install racing the rename in the same tmpdir. Mirror the proven cleanup.ts locked-error retry pattern with a short backoff, Windows-only. 3. Windows shards were badly imbalanced: the sharder weighted files by byte size, which concentrated every slow spawn/FS/lock-heavy file (snapshot, prompt, provider, run-process, instance-bootstrap, httpapi-session) into one shard (~612s vs ~356s siblings), and the resulting contention forced whole-file retries that doubled cost. Add TestShard.timedWeight and a committed test-timings.json seeded from CI junit data so shards balance by measured runtime (spread collapses from ~200s to ~18s) and contention-prone files spread across shards. Platforms without manifest entries fall back to size weighting. * fix(cli): skip stale manifest entries in timed shard weighting Bun.file().size returns 0 (never throws) for missing paths, so the try/catch in timedWeight was dead code and stale/renamed manifest entries added their time to the scale numerator with zero size, inflating the size-to-time ratio that estimates unknown files. Skip entries with a non-positive on-disk size instead of catching a throw that never happens. * revert(cli): drop hardcoded test-timings manifest The committed test-timings.json (482 entries) was a maintenance burden: it goes stale as tests are added/renamed and no size-based heuristic can replace it (slow subprocess outliers like run-process.test.ts are 7kb but 112s, 10x the runtime-per-byte of other files). Revert the timing-weighted sharding to the prior size-based LPT. The Windows reliability fixes (ConfigProvider filewatcher flag + Filesystem.write locked-file retry) remain and are what eliminate the failures and the ~360s of retry overhead that dominated the 12m50s shard. A maintainable runtime-based rebalance (self-updating CI cache fed from the junit artifacts CI already uploads) is a separate follow-up.
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.
Problem
Windows CLI unit tests were unreliable:
server/httpapi-instance-route-auth.test.tsfailed on Windows with an uncaughterror: Invalid handle(no stack), killing the PTY websocket auth test. This brokemainCI (run 30560035944, windows shard 1/4) and PRs.kilocode/server/config-overlay.test.tsintermittently returned HTTP 500 (UnknownError) on Windows (PR feat(agent-manager): run setup scripts in panel terminal #12703), and three files needed whole-file retries (run-process,instance-bootstrap,httpapi-session) — retry overhead dominated the slowest shard (PR feat(agent-manager): run setup scripts in panel terminal #12703 windows 4/4 ran 12m50s).Root causes and fixes
1. "Invalid handle" — config-provider blinding enabled the Windows file watcher on deleted temp dirs.
The test built its HttpApi app with
ConfigProvider.layer(ConfigProvider.fromUnknown({ KILO_SERVER_PASSWORD, KILO_SERVER_USERNAME })). In Effect,ConfigProvider.layerreplaces the ambient provider, so everyConfig.*read inside the app graph resolved against that two-key map andprocess.envbecame invisible. That blindedKILO_EXPERIMENTAL_DISABLE_FILEWATCHER, whichtest/preload.tsand CI set to\"true\"precisely to keep@parcel/watcherout of unit tests.With the flag hidden, the watcher's Windows backend subscribed on the temp repo's
.gitduring instance bootstrap. Theawait using tmpfixture then deleted that directory while the never-disposed per-test app runtime still held the subscription, andCreateFileWfailed with the hardcodedWatcherError(\"Invalid handle\")(the exact string lives in@parcel/watcher/src/windows/WindowsBackend.cc:93). The napi rejection carries no JS stack, which matches the bareerror: Invalid handleline in CI exactly. POSIX watcher backends (fs-events/inotify) tolerate a deleted watched dir; Windows does not, so the failure is Windows-only.Fix: add
KILO_EXPERIMENTAL_DISABLE_FILEWATCHERto every test config map that boots instances through the HttpApi app (httpapi-instance-route-auth,httpapi-cors,httpapi-ui, the exerciserbackend.ts,httpapi-kilo-edit,httpapi-memory). The flag is read from env with a\"true\"default, matching preload, so the watcher stays disabled in these tests. Auth semantics are unchanged —fromUnknowntreatsundefinedvalues as absent, and preload already scrubsKILO_SERVER_PASSWORD/KILO_SERVER_USERNAMEfrom env.2. config-overlay 500 — Windows locked-file errors on the atomic rename.
Filesystem.writewrites to a temp file thenrenames it into place. On POSIX that rename is atomic regardless of other activity in the directory. On Windows,MoveFileExfails transiently withEPERM/EACCES/EBUSYwhen Windows Defender/the search indexer or a concurrent writer (the detached Arborist plugin install thatconfig.loadInstanceStatekicks into the same tmpdir) briefly holds the freshly written temp file. The throw became an Effect defect → the error boundary'sUnknownError500. The repo already fights this exact Windows class intest/kilocode/cleanup.ts(60× retry withBun.gc), butFilesystem.writehad no retry.Fix: mirror the trusted
cleanup.tslocked()pattern — retryEBUSY/EACCES/EPERMwith a short linear backoff (50ms × attempt, up to 8 attempts on Windows, 1 elsewhere). POSIX is byte-identical (single attempt); the retry only fires under Windows contention and never changes success semantics. This also hardens real Windows users' config saves against the same Defender/indexer race.Follow-up (not in this PR)
The Windows shards are also imbalanced (the slowest concentrates the spawn/FS-heavy files), which adds wall time. Runtime-weighted sharding would fix it, but a committed timings manifest is a maintenance burden (it goes stale as tests change) and no static size heuristic can place small-but-slow subprocess outliers like
run-process.test.ts(7kb but 112s). A maintainable fix is a self-updating CI cache fed from the junit artifacts CI already uploads, which is tracked separately. The reliability fixes above already eliminate the failures and the retry overhead that dominated the 12m50s shard.