Skip to content

test(cli): stabilize issue 8656 stall test on Windows - #14152

Merged
marius-kilocode merged 2 commits into
mainfrom
fix/issue-8656-stall-test-flake
Sep 15, 2026
Merged

marius-kilocode merged 2 commits into
mainfrom
fix/issue-8656-stall-test-flake

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

What was flaky

packages/opencode/test/kilocode/issue-8656-stall.test.ts, case "recovers instead of freezing once the stall is bounded", failed only on the Windows shard unit (windows, 4/6). The until poll waited 90s for stalls > 0 && parts.includes("step-finish:tool-calls") and never saw it. The test-runner retry failed identically in a fresh process.

Root cause

In both failing runs the agent turn closed about half a second after the model catalog fetch, long before the 2s stall guard could fire, so the turn never reached the stall and the poll condition was permanently unsatisfiable. That is why raising the Windows budget from 60s to 90s in e958d44 changed nothing.

The fixture mirrored its state to disk with an awaited write-temp-then-rename inside the simulated provider fetch. That coupled the simulated provider response to disk IO in two ways:

  • a rejected write (Windows can refuse to replace a file that the test's 200ms state poll or Defender real-time scanning still holds open) rejected the simulated response, and the poisoned promise chain rejected every later one, so the turn ended with no tool call and stalls: 0;
  • a slow write delayed the response headers and raced the 2s request timeout the test configures.

Forcing one state write to reject locally reproduces the exact CI shape: state calls:1, stalls:0, timeline only the user text, poll exhausts the budget. The Windows-side trigger is inferred from that shape and from the runner log (Defender exclusions fail on the Windows runner); the CI log itself does not print the fixture error.

Change

Test code only, no product change:

  • fixture/stall-transport.ts: the response no longer waits on the state mirror. Writes stay serialized, retry transient Windows file errors (EPERM/EACCES/EBUSY), and a failed write is logged instead of propagated. Every write stores the full current state, so a later write still lands anything an earlier failed one dropped. readStallState tolerates a transient empty read.
  • issue-8656-stall.test.ts: after the turn goes idle, wait for the asynchronous recovery write before asserting on the state file.

The provider first-byte guard behavior is unchanged and still exercised.

Validation

  • bun test ./test/kilocode/issue-8656-stall.test.ts: 7 consecutive passes, timeline text | step-start | tool:bash:completed | step-finish:tool-calls | step-start | text | step-finish:stop, state {calls:4, stalls:1, recovered:1}.
  • Stress: with every state write delayed 500ms and the second write forced to fail, both cases still pass.
  • bun run typecheck from packages/opencode: pass.
  • bun run script/check-opencode-annotations.ts --worktree: no shared upstream files changed.

No changeset: test-only change.

The stall fixture mirrored its state to disk with an awaited write-temp then rename, running inside the simulated provider fetch. On Windows the rename can fail transiently while the test's 200ms state poll or Defender holds the destination open. A single failure rejected the simulated response, so the agent turn ended before the stall and the until() assertion could never succeed, which raising the budget could not fix.

Retry transient replace failures, treat the state file as diagnostics that must not reject the response, and tolerate a transient empty read.
@kilo-code-bot

kilo-code-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review of 130fdad2 (test-only follow-up to keep the stall fixture's state mirror off the response path). save() now queues the write on the serialized pending chain without awaiting it, and its .catch still absorbs failures, so disk latency or a rejected mirror can neither delay nor reject the simulated response. Write order is preserved because JSON.stringify(state) snapshots synchronously and the chain stays serialized, so a later write still carries every earlier increment. The added until(...) poll in the recovery test waits for the async write without racing the assertion that follows it. No dangling timers or unbounded promise-chain growth, and the test-only change correctly omits a changeset.

Files Reviewed (2 files)
  • packages/opencode/test/kilocode/fixture/stall-transport.ts
  • packages/opencode/test/kilocode/issue-8656-stall.test.ts
Previous Review Summary (commit ce18a2f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ce18a2f)

Status: No Issues Found | Recommendation: Merge

Test-only stabilization of the issue #8656 stall fixture. Verified that the bounded rename retry still propagates non-transient errors, the save() catch cannot reject the simulated provider response, and the new readStallState fallback returns a fresh object with no shared state. No memory-leak vectors in the retry timers or the serialized pending chain, and the test-only change correctly omits a changeset.

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/fixture/stall-transport.ts

Reviewed by deepseek-v4.1-flash · Input: 34.5K · Output: 5K · Cached: 240.9K

Review guidance: REVIEW.md from base branch main

The simulated provider response no longer waits on the state mirror, so fixture disk latency can never race the 2s request timeout. The bounded test waits for the asynchronous recovery write before asserting on the state file.
@marius-kilocode
marius-kilocode merged commit 95182b6 into main Sep 15, 2026
30 checks passed
@marius-kilocode
marius-kilocode deleted the fix/issue-8656-stall-test-flake branch September 15, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants