-
-
Notifications
You must be signed in to change notification settings - Fork 313
ci: auto-retry test runs when every failed job matches a known-transient pattern #849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| name: Auto-retry known-transient CI flakes | ||
|
|
||
| # Careful, narrow auto-healing for this repo's OWN test CI - not a blanket | ||
| # "retry until green" mechanism. game-ci/cli's Unity build/activate steps | ||
| # already retry a specific, documented set of transient licensing errors | ||
| # in-process (up to --licenseRetryMaxAttempts, default 4, exponential | ||
| # backoff - see dist/platforms/{mac,ubuntu}/steps/{activate,build}.sh). That | ||
| # retries on the SAME runner instance, so it can't help when the underlying | ||
| # issue is runner-specific (e.g. a stuck Gatekeeper/codesign cache) rather | ||
| # than a genuinely transient network blip - all 4 in-process attempts then | ||
| # fail identically, and only a fresh job (potentially a different runner) | ||
| # has a chance. | ||
| # | ||
| # This workflow closes exactly that gap: if a build-tests-* run finishes | ||
| # with failed jobs, and EVERY failed job's log matches the exact same | ||
| # known-transient pattern the in-process retry already uses (nothing | ||
| # broader - a real compile error, a genuine license misconfiguration, or | ||
| # any other failure never matches and is never auto-retried), rerun the | ||
| # failed jobs once. If any failed job doesn't match, or this is already a | ||
| # retry (workflow_run.run_attempt > 1), do nothing - a human or agent needs | ||
| # to look at it. | ||
| on: | ||
| workflow_run: | ||
| workflows: ['Builds - MacOS', 'Builds - Ubuntu', 'Builds - Windows'] | ||
| types: [completed] | ||
|
|
||
| permissions: | ||
| actions: write | ||
| contents: read | ||
|
|
||
| jobs: | ||
| retry-if-known-flake: | ||
| name: Retry if known-transient flake | ||
| runs-on: ubuntu-latest | ||
| if: github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt == 1 | ||
| steps: | ||
| - name: Check failed jobs against the known-transient pattern, rerun if all match | ||
| uses: actions/github-script@v7 | ||
| env: | ||
| # Kept in sync with dist/platforms/{mac,ubuntu}/steps/{activate,build}.sh's | ||
| # own UNITY_*_TRANSIENT_LICENSE_ERROR_PATTERN - update both together. | ||
| TRANSIENT_PATTERN: 'TimeoutPolicy did not complete|Access token is unavailable|entitlement groups and 0 free entitlements|License activation has failed|No valid Unity Editor license found|License is not active' | ||
| with: | ||
| script: | | ||
| const runId = context.payload.workflow_run.id; | ||
| const pattern = new RegExp(process.env.TRANSIENT_PATTERN); | ||
|
|
||
| const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| run_id: runId, | ||
| per_page: 100, | ||
| }); | ||
|
Comment on lines
+48
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
file=".github/workflows/auto-retry-known-flakes.yml"
sed -n '1,140p' "$file"
printf '\n--- related API usage ---\n'
rg -n -C 4 'listJobsForWorkflowRun|reRunWorkflowFailedJobs|allMatch|TRANSIENT_PATTERN|runId' "$file"Repository: game-ci/unity-builder Length of output: 6725 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows'
printf '%s\n' '--- triggering workflow definitions and matrices ---'
rg -n -i -C 3 "name: Builds - (MacOS|Ubuntu|Windows)|matrix:|strategy:|include:|exclude:" .github/workflowsRepository: game-ci/unity-builder Length of output: 27988 🏁 Script executed: #!/bin/bash
set -eu
for file in .github/workflows/build-tests-mac.yml .github/workflows/build-tests-ubuntu.yml .github/workflows/build-tests-windows.yml; do
echo "--- $file ---"
sed -n '1,115p' "$file"
doneRepository: game-ci/unity-builder Length of output: 13062 Paginate before classifying failed jobs. If a workflow run exceeds 100 jobs, 🤖 Prompt for AI Agents |
||
|
|
||
| const failedJobs = jobs.filter((job) => job.conclusion === 'failure'); | ||
| if (failedJobs.length === 0) { | ||
| core.info('No failed jobs found (a cancelled/skipped-only run) - nothing to do.'); | ||
| return; | ||
| } | ||
|
|
||
| let allMatch = true; | ||
| for (const job of failedJobs) { | ||
| let log = ''; | ||
| try { | ||
| const response = await github.rest.actions.downloadJobLogsForWorkflowRun({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| job_id: job.id, | ||
| }); | ||
| log = response.data.toString(); | ||
| } catch (error) { | ||
| core.warning(`Could not fetch logs for job ${job.name} (${job.id}): ${error.message}`); | ||
| allMatch = false; | ||
| break; | ||
| } | ||
|
|
||
| if (!pattern.test(log)) { | ||
| core.info(`Job "${job.name}" failed without the known-transient pattern - not auto-retrying this run.`); | ||
| allMatch = false; | ||
| break; | ||
| } | ||
|
Comment on lines
+77
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Classify the terminal failure, not any log occurrence.
🤖 Prompt for AI Agents |
||
| core.info(`Job "${job.name}" failed with the known-transient pattern.`); | ||
| } | ||
|
|
||
| if (!allMatch) return; | ||
|
|
||
| core.info(`All ${failedJobs.length} failed job(s) matched the known-transient pattern - rerunning failed jobs.`); | ||
| await github.rest.actions.reRunWorkflowFailedJobs({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| run_id: runId, | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: game-ci/unity-builder
Length of output: 4247
Security Misconfiguration (CWE-829): Inclusion of Functionality from Untrusted Control Sphere
Exploitability: Difficult
Pin
actions/github-scriptto a full commit SHA.This privileged
workflow_runjob uses the mutableactions/github-script@v7tag withactions: write. Pin the action to a verified full commit SHA.🤖 Prompt for AI Agents
Source: Linters/SAST tools