From f79183365ebdfcb9ecb2738afaf4697176c5b9e4 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Mon, 1 Jun 2026 21:45:00 +0800 Subject: [PATCH 1/3] ci: retry windows-advisory unit step once on transient failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The windows-advisory workflow has shown ~40% red over the last ten runs on dev, driven by two transient failure modes that are not regressions from the merged PR: - Effect-TS sleep-based timing assertions in opencode session tests (e.g. SessionRunState defers disposeAll) that flake on the Windows runner's coarser wall clock. - Native Bun crashes on windows-latest (segfault in watcher.node frames in #1028's run), independent of test content. Linux ci is the load-bearing required gate; this workflow is advisory and non-blocking. Wrap each matrix shard's unit step in a process-level retry (max_attempts=2) so a single transient failure does not turn the advisory red. Keep the first-attempt failure visible in the step summary ("Windows unit attempt 1 failed (retrying)" / "Windows unit recovered on retry") so persistent regressions are not hidden by the retry. Each attempt runs in a subshell so cd packages/... in matrix.command does not leak across attempts. No third-party retry action — the repo pins all action uses by SHA and a small bash loop avoids an extra dependency. --- .github/workflows/windows-advisory.yml | 51 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows-advisory.yml b/.github/workflows/windows-advisory.yml index 58cedb176..f31cfe46d 100644 --- a/.github/workflows/windows-advisory.yml +++ b/.github/workflows/windows-advisory.yml @@ -258,19 +258,62 @@ jobs: - name: unit id: unit + # Windows-advisory has shown transient flake (sleep-based timing in + # opencode session tests and occasional Bun native crashes on + # `windows-latest`). Linux `ci` is the load-bearing required gate; + # this workflow is advisory and non-blocking. Retry once at the + # process level so a single transient failure does not turn the + # advisory red, but keep the first-attempt failure visible in the + # step summary so persistent regressions are not hidden by retry. + # Each attempt runs in a subshell so `cd packages/...` in + # matrix.command does not leak between attempts. run: | set +e - ${{ matrix.command }} - status=$? + attempts=2 + first_status= + for attempt in $(seq 1 "$attempts"); do + echo "::group::Windows unit attempt $attempt of $attempts" + ( ${{ matrix.command }} ) + status=$? + echo "::endgroup::" + + if [ "$attempt" -eq 1 ]; then + first_status=$status + fi + + if [ "$status" -eq 0 ]; then + break + fi + + if [ "$attempt" -lt "$attempts" ]; then + { + echo "### Windows unit attempt $attempt failed (retrying)" + echo "" + echo "- package: ${{ matrix.package }}" + echo "- exit code: $status" + echo "- next attempt: $((attempt + 1)) of $attempts" + } >> "$GITHUB_STEP_SUMMARY" + fi + done + echo "exit_code=$status" >> "$GITHUB_OUTPUT" + echo "first_exit_code=$first_status" >> "$GITHUB_OUTPUT" - if [ "$status" -ne 0 ]; then + if [ "$status" -eq 0 ] && [ "$first_status" -ne 0 ]; then + { + echo "### Windows unit recovered on retry" + echo "" + echo "- package: ${{ matrix.package }}" + echo "- attempt 1 exit code: $first_status" + echo "- attempt 2: success" + } >> "$GITHUB_STEP_SUMMARY" + elif [ "$status" -ne 0 ]; then { echo "### Windows unit diagnostic" echo "" echo "- package: ${{ matrix.package }}" echo "- exit code: $status" - echo "- status: failed advisory signal" + echo "- status: failed advisory signal after $attempts attempts" } >> "$GITHUB_STEP_SUMMARY" fi From 30dfa2dfce6f7723fe3019b28153daaf6155ae73 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Mon, 1 Jun 2026 21:59:13 +0800 Subject: [PATCH 2/3] test(ci): pin Windows unit retry contract in workflow self-test Address review feedback: the new retry semantics on the windows-advisory unit step had no self-test fixation, so future changes to the retry count, exported first-attempt exit code, or recovery-summary headers could silently regress without breaking ci-workflow.test.ts. Add a focused "retries the Windows unit step once on transient failure" test that pins: - attempts=2 (single retry budget) - ( ${{ matrix.command }} ) subshell so cd does not leak between attempts - first_exit_code GITHUB_OUTPUT export - First-attempt failure summary header and recovery summary header - Final exit uses the last attempt's status, not the first --- .../opencode/test/github/ci-workflow.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/opencode/test/github/ci-workflow.test.ts b/packages/opencode/test/github/ci-workflow.test.ts index efe3aa84e..e4e4749f8 100644 --- a/packages/opencode/test/github/ci-workflow.test.ts +++ b/packages/opencode/test/github/ci-workflow.test.ts @@ -609,6 +609,30 @@ describe("ci workflow", () => { ) }) + test("retries the Windows unit step once on transient failure", () => { + const unitRun = stepByName(windowsUnitJobName, "unit", windowsAdvisoryWorkflowPath)?.run ?? "" + + // Retry budget: exactly one extra attempt (max_attempts=2). + expect(unitRun).toContain("attempts=2") + + // Each attempt runs in a subshell so `cd packages/...` in matrix.command + // does not leak working directory across attempts. + expect(unitRun).toContain("( ${{ matrix.command }} )") + + // First-attempt exit code must be exported so downstream steps and humans + // can tell a recovered run apart from a clean-first-pass run. + expect(unitRun).toContain('echo "first_exit_code=$first_status" >> "$GITHUB_OUTPUT"') + + // First-attempt failure is surfaced in the step summary even when the + // retry recovers — the advisory must not silently swallow transient flake. + expect(unitRun).toContain("### Windows unit attempt $attempt failed (retrying)") + expect(unitRun).toContain("### Windows unit recovered on retry") + + // The final exit code is the last attempt's status, not the first. + // Otherwise a recovered run would still turn the advisory red. + expect(unitRun).toMatch(/exit "\$status"\s*$/) + }) + test("defines Windows unit packages and opencode shards", () => { const parsed = parseWorkflow(windowsAdvisoryWorkflowPath) const job = parsed.jobs?.[windowsUnitJobName] From f9a2c65f3eaa2b3f33c6ee7fc51cc6d22ce1101e Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Mon, 1 Jun 2026 22:34:59 +0800 Subject: [PATCH 3/3] ci: emit annotation when Windows unit retry recovers or finally fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback: recovery from a retry currently only writes a step-summary header, so the first-attempt failure is easy to miss when scanning the PR checks list or the run's annotations panel — making the advisory's true flake rate hard to track. Emit a GitHub Actions workflow command on the two terminal outcomes of the retry loop: - `::notice title=Windows unit recovered on retry::...` when attempt 1 failed but attempt 2 succeeded. Surfaces in the run-level annotations panel and per-check annotation list — observable without expanding the step summary. - `::warning title=Windows unit failed advisory signal after retry::...` when both attempts failed. The advisory still goes red on this path, but the warning annotation makes the post-retry failure scannable alongside the run-level red icon. Update ci-workflow.test.ts to pin both annotation strings, so future edits to the retry contract cannot silently drop them. --- .github/workflows/windows-advisory.yml | 8 ++++++++ packages/opencode/test/github/ci-workflow.test.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/windows-advisory.yml b/.github/workflows/windows-advisory.yml index f31cfe46d..960414e10 100644 --- a/.github/workflows/windows-advisory.yml +++ b/.github/workflows/windows-advisory.yml @@ -300,6 +300,10 @@ jobs: echo "first_exit_code=$first_status" >> "$GITHUB_OUTPUT" if [ "$status" -eq 0 ] && [ "$first_status" -ne 0 ]; then + # ::notice surfaces the recovery in the run-level annotations panel + # and in each check's annotation list, not just buried in the + # collapsed step summary, so flake rate stays observable. + echo "::notice title=Windows unit recovered on retry::package=${{ matrix.package }}; attempt 1 exit code=$first_status; attempt 2 succeeded" { echo "### Windows unit recovered on retry" echo "" @@ -308,6 +312,10 @@ jobs: echo "- attempt 2: success" } >> "$GITHUB_STEP_SUMMARY" elif [ "$status" -ne 0 ]; then + # ::warning gives the run-level annotation a yellow icon on a red + # job — the step is still failing (advisory signal red), but the + # annotation makes the post-retry failure scannable at a glance. + echo "::warning title=Windows unit failed advisory signal after retry::package=${{ matrix.package }}; final exit code=$status; attempts=$attempts" { echo "### Windows unit diagnostic" echo "" diff --git a/packages/opencode/test/github/ci-workflow.test.ts b/packages/opencode/test/github/ci-workflow.test.ts index e4e4749f8..d86d379d3 100644 --- a/packages/opencode/test/github/ci-workflow.test.ts +++ b/packages/opencode/test/github/ci-workflow.test.ts @@ -628,6 +628,12 @@ describe("ci workflow", () => { expect(unitRun).toContain("### Windows unit attempt $attempt failed (retrying)") expect(unitRun).toContain("### Windows unit recovered on retry") + // The recovered and final-failed outcomes also emit run-level annotations + // (::notice / ::warning) so the signal is visible in the Actions UI + // annotations panel, not only inside the collapsed step summary. + expect(unitRun).toContain("::notice title=Windows unit recovered on retry") + expect(unitRun).toContain("::warning title=Windows unit failed advisory signal after retry") + // The final exit code is the last attempt's status, not the first. // Otherwise a recovered run would still turn the advisory red. expect(unitRun).toMatch(/exit "\$status"\s*$/)