fix(ci): restore green Windows CI and make timed-out suites diagnosable - #2066
Merged
Conversation
greptile-apps
Bot
dismissed
their stale review
July 29, 2026 19:10
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
flora131
enabled auto-merge (squash)
July 29, 2026 19:10
greptile-apps
Bot
dismissed
their stale review
July 29, 2026 19:32
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
The Windows `test` job has been killed at its 15m `timeout-minutes` budget on every run since #2064, always inside the coding-agent vitest step. The step log was empty for the entire 10m before the kill, so the cancellation carried no evidence about which test stalled. The cause of the blackout is `run-flaky-test-suite.ts`: it collected the whole child stdout/stderr with `new Response(child.stdout).text()` and only wrote it after the child exited. A child killed by the job timeout never exits normally, so every collected line was discarded. Tee both streams as they arrive instead. The retry, deterministic-file, and diagnostics-artifact contracts are unchanged; a timed-out attempt now leaves its progress in the step log. Also raise the Windows budget to 30m temporarily so the suite can run to completion and report its real failures. Restored once the stall is fixed. Assistant-model: Claude Opus 5
The deterministic CI contract pins the Windows timeout at 15m, so the temporary diagnostic bump failed 'Deterministic CI and release contracts' before the vitest step could run. Streamed output alone now carries the evidence: the killed step reports progress up to the kill point. Assistant-model: Claude Opus 5
…rewrite #2064 rewrote these assertions and dropped the platform-aware handling the previous versions had, so all three fail on Windows only: - footer-width: `formatCwdForFooter` joins with `sep`, so the expectation is `~\project` on Windows. The rewrite hardcoded `~/project` (was: `` `~${sep}project` ``). - sdk-session-manager: the session file separator check hardcoded `/` (was: `` `${expectedSessionDir}${sep}` ``). - sdk-session-manager: the system prompt always renders cwd with POSIX separators (`system-prompt.ts` applies `.replace(/\\/g, "/")`), so the Windows expectation needs the same normalization (was: `` sessionCwd.replaceAll("\\", "/") ``). Restore all three. Production code is unchanged and was correct throughout. This is what exhausted the Windows job budget: the three failures triggered the bounded retry, and two full ~170s suite runs pushed the job past its 15m cap, where GitHub reported the kill as `cancelled`. Assistant-model: Claude Opus 5
With the separator fixes in place the Windows suite ran far enough to expose two more failures, both from tests that assume POSIX process and filesystem behavior: - bash-session-metadata: the detached-async-job test leaves a shell running with the temp dir as its cwd. Windows locks a directory in that state, so `afterEach` cleanup failed with EBUSY and failed the test. Retry the removal and ignore a residual failure; reclaiming an OS temp dir is never the assertion. - sdk-session-manager: the cwd check compared `pwd` output through `realpathSync`. On Windows the bash tool resolves to Git Bash, which reports MSYS paths (`/tmp/...`) that Node resolves against the current drive, so the lstat failed with ENOENT on `C:\tmp\...`. Prove the working directory by reading a marker file only resolvable from sessionCwd, which tests the same contract without depending on how a shell spells a path. Assistant-model: Claude Opus 5
flora131
force-pushed
the
fix/ci-test-failures
branch
from
July 29, 2026 19:36
dfcb41a to
7ad68bf
Compare
All 374 Windows test files now pass in 191s, but the process then sits for ~412s until the job cap kills it. The last green Windows run (pre-#2064) exited 0.8s after printing its summary, so the exit hang is new and is what still exhausts the budget. Add vitest's hanging-process reporter to name the handle. Removed once the leak is fixed. Assistant-model: Claude Opus 5
greptile-apps
Bot
dismissed
their stale review
July 29, 2026 20:00
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
The Windows suite now passes all 374 files in ~200s, then sits until the job cap kills it. vitest's hanging-process reporter printed nothing, because the suite runs under `bun --bun` where that detection does not apply. Swap it for a global teardown that dumps the live process tree on Windows, to name whatever still holds the event loop open. The failure path exits in 0.2s while the success path hangs, which points at a surviving child rather than a reporting bug. Removed once the leak is fixed. Assistant-model: Claude Opus 5
The teardown process-tree dump showed no surviving bash/sleep/git children and no leftover vitest workers, so the handle that blocks exit lives inside vitest's own process rather than in spawned test children. Replace the one-shot dump with unref'd probes at +15s and +45s that report process.getActiveResourcesInfo(). Unref'd timers fire only while the loop is still alive, so a healthy run exits before they run and pays nothing. Assistant-model: Claude Opus 5
The interactive OAuth callback test drives a real LoginDialogComponent through onAuth, and showAuthUrl calls openBrowser. On Windows that spawns a detached `rundll32 url.dll,FileProtocolHandler https://corp.invalid/login`, which inherits the test process's stdout/stderr write handles. The CI runner has no browser to hand off to, so rundll32 never exits: the vitest process, `bun run`, and every descendant terminate, but the step's pipes never reach EOF and the job hangs until its 15-minute budget expires. CI evidence (run 30495631917, Windows job): all 2887 tests pass, the child exits code=0 at 22:25:38, and a Win32_Process table taken every 30s after that shows exactly one non-infrastructure survivor, pid 2508 `rundll32 url.dll,FileProtocolHandler https://corp.invalid/login`, started 22:24:23 with a dead parent, still alive when the job is cancelled. 5ef323e added this test, which is why the bisect lands there. Mock the launcher exactly as pi's login-dialog suites do, and assert the dialog still hands the URL to it so the launch path stays covered without spawning a process. Assistant-model: Claude Opus 5
The probes named the leaking process, so drop them: the vitest globalSetup that dumped the surviving process tree, its config entry, and the process table the flaky-suite wrapper printed after the child exited. The streamed tee stays, since a timed-out attempt still needs a self-describing log. Assistant-model: Claude Opus 5
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
The
Testsworkflow failed on every run since #2064. The Windows job was killed at its 15-minutetimeout-minutesbudget, always inside thecoding-agent vitest suitestep. GitHub reports atimeout-minuteskill ascancelled, which is why this read as noise rather than a red build.fix(workflows)fix(auth)ModelRuntimefix(copilot)Root cause: a test launched a real browser on the CI runner
test/interactive-auth-login.test.tsdrives a realLoginDialogComponentthrough theonAuthcallback.showAuthUrlcallsopenBrowser(url), and on Windows that is:spawned detached. Node's
spawnon Windows creates the child with handle inheritance, sorundll32inherits the test process's stdout/stderr write handles. The runner has no browser to hand the URL to, sorundll32never exits.A GitHub Actions
run:step ends only when the child exits and every inherited stdout/stderr handle closes. So the whole visible chain finished normally — 2887 tests passed in ~135s, vitest exited,bun runexited, the wrapper's direct child exited — and the step still hung on pipes that could never reach EOF, until the 15-minute budget killed the job.#2064 is the first bad commit because #2064 added that test case (
honors transported callback metadata and resolves manual redirect input). At86dd67d6c, the last green commit, this file never invokedonAuth.Evidence
Run 30495631917, Windows job, with a temporary probe that dumped
Win32_Processevery 30s after the child exited:Every 30s sample through cancellation showed the same single non-infrastructure survivor, started mid-suite, with a dead parent. No
bun.exeornode.exedescendant survived — the earlier "leaked engine/RPC child" theory is refuted; the twobun.exeand onenode.exeseen at teardown in the first report were the livebun run→vitestchain itself.Fix
Mock the launcher, exactly as pi's own login-dialog suites do (
test/suite/regressions/5433-extension-oauth-prompt-input.test.ts):and assert the dialog still hands the URL to it, so the launch path stays covered without spawning a process:
That assertion is the regression test: if a future change makes this suite reach the real launcher again, it fails rather than hanging Windows CI.
No shipped behavior changes, so there is no changelog entry.
src/utils/open-browser.tsis untouched and still matches upstream pi.Also in this PR
Three Windows-only test failures from #2064, which dropped platform-aware handling and made the bounded flake retry run the suite twice:
footer-width— home abbreviation"~/project"`~${sep}project`sdk-session-manager— session file prefix`${expectedSessionDir}/``${expectedSessionDir}${sep}`sdk-session-manager— prompt cwd${sessionCwd}sessionCwd.replaceAll("\\", "/")Production code was correct throughout:
formatCwdForFooterjoins withsep, andsystem-prompt.tsrenders cwd with POSIX separators.scripts/run-flaky-test-suite.tsalso buffered the child's entire output vianew Response(child.stdout).text()and wrote it only after the child exited, so a child killed by the job timeout discarded every collected line — the cancelled step logged minutes of silence. It now tees both streams as they arrive. Retry, deterministic-file, and diagnostics-artifact behavior are unchanged (test/unit/flaky-test-suite-runner.test.tspasses). That change is what produced the evidence above and keeps the next timeout self-describing.bash-session-metadatacleanup now retries around transient Windows directory locks.Verification
8918590d8—test (blacksmith-4vcpu-windows-2025, windows-x64)succeeded in 8m30s (22:46:44 → 22:55:14) against the unchanged 15-minute budget. Thecoding-agent vitest suitestep took 160s and ended on its own. Linux green in the same run.packages/coding-agent/test-exit-diagnostic.ts, itsglobalSetupentry, and the wrapper's process-table dump) are removed; the green run above does not contain them.bun run typecheck,bun run lint,bun run check:file-length,bun run test:unit(4406 pass / 0 fail) all green.timeout-minuteswas widened, no survivor is force-killed from CI YAML, and no suite is skipped or excluded.Ruled out during triage
globalThis.fetchacross all 370 test files recorded exactly one outbound request.rundll32as the only survivor.Greptile Summary
Restores Windows CI reliability and improves diagnostics for timed-out test suites.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains; the Windows timeout contract issue is fixed because the workflow retains the required 15-minute budget.
What T-Rex did
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD Start["Run test suite attempt"] --> Spawn["Spawn child with stdout/stderr pipes"] Spawn --> Tee["Stream both pipes to CI log and capture output"] Tee --> Exit{"Attempt succeeds?"} Exit -->|Yes| Done["Complete successfully"] Exit -->|No| Retry["Run bounded retry"] Retry --> TeeRetry["Stream retry output live"] TeeRetry --> Persist["Persist diagnostics artifact"] Persist --> Result{"Retry succeeds?"} Result -->|Yes| Done Result -->|No| Fail["Exit non-zero with visible diagnostics"]Reviews (10): Last reviewed commit: "test: remove the Windows teardown diagno..." | Re-trigger Greptile