Notify on Failure #205
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
| name: Notify on Failure | |
| # Fires whenever the CI or Release workflow completes with a failure conclusion. | |
| # Creates (or updates) a GitHub Issue so failures are always visible in the | |
| # issue tracker — no need to check the Actions tab manually. | |
| on: | |
| workflow_run: | |
| workflows: ["CI", "Release"] | |
| types: [completed] | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| notify: | |
| name: Create failure issue | |
| # Only run when the triggering workflow actually failed | |
| if: github.event.workflow_run.conclusion == 'failure' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Create or update ci-failure issue | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const run = context.payload.workflow_run; | |
| const shortSha = run.head_sha.substring(0, 8); | |
| // ── Ensure the ci-failure label exists ────────────────────────── | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner, repo, | |
| name: 'ci-failure', | |
| color: 'e11d48', | |
| description: 'Automated: CI/Release workflow failure', | |
| }); | |
| } catch (e) { | |
| if (e.status !== 422) throw e; // 422 = already exists — fine | |
| } | |
| const title = `🔴 ${run.name} failed on \`${run.head_branch}\``; | |
| const body = [ | |
| `## Workflow Failure Detected`, | |
| ``, | |
| `| Field | Value |`, | |
| `|-------|-------|`, | |
| `| **Workflow** | ${run.name} |`, | |
| `| **Branch** | \`${run.head_branch}\` |`, | |
| `| **Commit** | [\`${shortSha}\`](https://github.com/${owner}/${repo}/commit/${run.head_sha}) |`, | |
| `| **Run** | [#${run.run_number} — view logs](${run.html_url}) |`, | |
| `| **Triggered by** | \`${run.event}\` |`, | |
| ``, | |
| `### How to debug`, | |
| `1. Open the **[run log](${run.html_url})** and expand the ❌ failed step`, | |
| `2. Trigger the **[Debug Diagnostics](../actions/workflows/debug.yml)** workflow manually for a full environment dump`, | |
| `3. Fix the issue locally, then push to re-trigger CI`, | |
| `4. Close this issue once the next CI run is green ✅`, | |
| ``, | |
| `> _Auto-generated by [notify-failure.yml](.github/workflows/notify-failure.yml)_`, | |
| ].join('\n'); | |
| // ── Avoid duplicates: update existing open issue if one exists ── | |
| const { data: existing } = await github.rest.issues.listForRepo({ | |
| owner, repo, state: 'open', labels: 'ci-failure', per_page: 20, | |
| }); | |
| const dupe = existing.find(i => i.title.includes(run.name)); | |
| if (dupe) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, | |
| issue_number: dupe.number, | |
| body: [ | |
| `### Failure repeated — ${new Date().toUTCString()}`, | |
| ``, | |
| `[Run #${run.run_number}](${run.html_url}) failed again on commit [\`${shortSha}\`](https://github.com/${owner}/${repo}/commit/${run.head_sha}).`, | |
| ].join('\n'), | |
| }); | |
| core.notice(`Updated existing failure issue #${dupe.number}`); | |
| } else { | |
| const { data: issue } = await github.rest.issues.create({ | |
| owner, repo, title, body, | |
| labels: ['ci-failure'], | |
| }); | |
| core.notice(`Created failure issue #${issue.number}: ${issue.html_url}`); | |
| } | |
| close-on-success: | |
| name: Close failure issue on success | |
| # Auto-close any open ci-failure issues when the triggering workflow succeeds | |
| if: github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Close open ci-failure issues for this workflow | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const run = context.payload.workflow_run; | |
| const shortSha = run.head_sha.substring(0, 8); | |
| const { data: existing } = await github.rest.issues.listForRepo({ | |
| owner, repo, state: 'open', labels: 'ci-failure', per_page: 20, | |
| }); | |
| const toClose = existing.filter(i => i.title.includes(run.name)); | |
| for (const issue of toClose) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, | |
| issue_number: issue.number, | |
| body: [ | |
| `### ✅ ${run.name} is green — auto-closing`, | |
| ``, | |
| `[Run #${run.run_number}](${run.html_url}) on [\`${shortSha}\`](https://github.com/${owner}/${repo}/commit/${run.head_sha}) completed successfully.`, | |
| ].join('\n'), | |
| }); | |
| await github.rest.issues.update({ | |
| owner, repo, | |
| issue_number: issue.number, | |
| state: 'closed', | |
| }); | |
| core.notice(`Auto-closed failure issue #${issue.number}`); | |
| } |