Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions .github/workflows/windows-advisory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,19 +258,70 @@ 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
# ::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 ""
echo "- package: ${{ matrix.package }}"
echo "- attempt 1 exit code: $first_status"
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 ""
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

Expand Down
30 changes: 30 additions & 0 deletions packages/opencode/test/github/ci-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,36 @@ 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 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*$/)
})

test("defines Windows unit packages and opencode shards", () => {
const parsed = parseWorkflow(windowsAdvisoryWorkflowPath)
const job = parsed.jobs?.[windowsUnitJobName]
Expand Down
Loading