Repository navigation
PR Workflow Failure Notifier #2929
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
| # 🚨 COPILOT PROHIBITION WARNING 🚨 | |
| # | |
| # THIS FILE IS CRITICAL TO DEPLOYMENT WORKFLOW MONITORING AND PR MANAGEMENT | |
| # | |
| # ⛔ COPILOT AGENTS ARE STRICTLY PROHIBITED FROM MAKING ANY CHANGES TO THIS FILE | |
| # ⛔ WITHOUT EXPLICIT WRITTEN CONSENT FROM THE REPOSITORY OWNER (@litlfred) | |
| # | |
| # This workflow monitors deployment failures and manages PR notifications. | |
| # Changes can disrupt the deployment feedback system and PR management. | |
| # | |
| # 🔒 REQUIRED PROCESS FOR CHANGES: | |
| # 1. Request explicit consent from @litlfred in a GitHub comment | |
| # 2. Wait for written approval before making ANY changes | |
| # 3. Document the explicit consent in the commit message | |
| # 4. Test extensively in a separate environment before merging | |
| # | |
| # 🚨 END PROHIBITION WARNING 🚨 | |
| name: PR Workflow Failure Notifier | |
| # This workflow runs after any workflow fails and posts a rich comment on the associated PR | |
| # It provides: | |
| # - Link to repository | |
| # - Link to workflow run (for logs) | |
| # - Link to workflow YAML definition file | |
| # - Workflow chain information | |
| # - Error log information | |
| # Requirements met: | |
| # - Minimal intrusion: runs separately using workflow_run trigger | |
| # - Centralized failure detection: monitors all workflows in one place | |
| # - Rich PR comments: includes all required metadata and links | |
| # - No per-workflow customization needed: automatic detection | |
| # - Maintainable: single file with robust error handling | |
| on: | |
| workflow_run: | |
| workflows: [ | |
| "Deploy Feature Branch", | |
| "Deploy Landing Page", | |
| "PR Commit Feedback", | |
| "PR Review Deployment", | |
| "Code Quality Checks" | |
| ] | |
| types: | |
| - completed | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| notify-pr-on-failure: | |
| # Only run if the workflow failed | |
| if: ${{ github.event.workflow_run.conclusion == 'failure' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Get workflow failure details and find associated PR | |
| id: failure_details | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const { workflow_run } = context.payload; | |
| console.log(`Workflow "${workflow_run.name}" failed`); | |
| console.log(`Run ID: ${workflow_run.id}`); | |
| console.log(`Conclusion: ${workflow_run.conclusion}`); | |
| // Get associated PRs for this workflow run | |
| const prs = workflow_run.pull_requests || []; | |
| if (prs.length === 0) { | |
| console.log('No associated PRs found for this workflow run'); | |
| core.setOutput('has_pr', 'false'); | |
| return; | |
| } | |
| const pr = prs[0]; // Use the first PR if multiple | |
| console.log(`Found associated PR #${pr.number}`); | |
| // Gather workflow metadata | |
| const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`; | |
| const runUrl = workflow_run.html_url; | |
| // Try to determine workflow file name from the workflow name | |
| let workflowFileName = ''; | |
| const workflowNameMap = { | |
| 'Deploy Feature Branch': 'branch-deployment.yml', | |
| 'Deploy Landing Page': 'landing-page-deployment.yml', | |
| 'PR Commit Feedback': 'pr-commit-feedback.yml', | |
| 'PR Review Deployment': 'review.yml', | |
| 'Code Quality Checks': 'code-quality.yml' | |
| }; | |
| workflowFileName = workflowNameMap[workflow_run.name] || | |
| (workflow_run.name.toLowerCase().replace(/\s+/g, '-') + '.yml'); | |
| const workflowYamlUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/.github/workflows/${workflowFileName}`; | |
| // Analyze workflow chain - check if this was triggered by another workflow | |
| let chainInfo = `- **Workflow**: ${workflow_run.name} ([YAML](${workflowYamlUrl}))`; | |
| if (workflow_run.triggering_actor) { | |
| chainInfo += `\n ↳ **Triggered by**: ${workflow_run.triggering_actor.login}`; | |
| } | |
| // Add trigger event information | |
| if (workflow_run.event) { | |
| chainInfo += `\n ↳ **Event**: ${workflow_run.event}`; | |
| } | |
| // Store all the information for the next step | |
| core.setOutput('has_pr', 'true'); | |
| core.setOutput('pr_number', pr.number.toString()); | |
| core.setOutput('workflow_name', workflow_run.name); | |
| core.setOutput('run_id', workflow_run.id.toString()); | |
| core.setOutput('repo_url', repoUrl); | |
| core.setOutput('run_url', runUrl); | |
| core.setOutput('workflow_yaml_url', workflowYamlUrl); | |
| core.setOutput('workflow_file_name', workflowFileName); | |
| core.setOutput('chain_info', chainInfo); | |
| core.setOutput('head_sha', workflow_run.head_sha); | |
| core.setOutput('head_branch', workflow_run.head_branch); | |
| - name: Get workflow logs for error details | |
| id: get_logs | |
| if: steps.failure_details.outputs.has_pr == 'true' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const runId = '${{ steps.failure_details.outputs.run_id }}'; | |
| try { | |
| // Get the jobs for this workflow run to find failed jobs | |
| const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId | |
| }); | |
| console.log(`Found ${jobs.total_count} jobs for run ${runId}`); | |
| // Find failed jobs | |
| const failedJobs = jobs.jobs.filter(job => job.conclusion === 'failure'); | |
| console.log(`Found ${failedJobs.length} failed jobs`); | |
| let errorSummary = ''; | |
| if (failedJobs.length > 0) { | |
| errorSummary = '### Failed Jobs:\n'; | |
| for (const job of failedJobs.slice(0, 3)) { // Limit to first 3 failed jobs | |
| errorSummary += `- **${job.name}**: ${job.conclusion}\n`; | |
| if (job.steps) { | |
| const failedSteps = job.steps.filter(step => step.conclusion === 'failure'); | |
| if (failedSteps.length > 0) { | |
| errorSummary += ` - Failed steps: ${failedSteps.map(s => s.name).join(', ')}\n`; | |
| } | |
| } | |
| } | |
| if (failedJobs.length > 3) { | |
| errorSummary += `- ... and ${failedJobs.length - 3} more failed jobs\n`; | |
| } | |
| } else { | |
| errorSummary = '### Error Details:\nJob failure details not available. Check the workflow logs for more information.\n'; | |
| } | |
| core.setOutput('error_summary', errorSummary); | |
| } catch (error) { | |
| console.error('Error fetching job details:', error); | |
| core.setOutput('error_summary', '### Error Details:\nUnable to fetch detailed error information. Check the workflow logs for more details.\n'); | |
| } | |
| - name: Post failure notification comment on PR | |
| if: steps.failure_details.outputs.has_pr == 'true' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const prNumber = parseInt('${{ steps.failure_details.outputs.pr_number }}'); | |
| const workflowName = '${{ steps.failure_details.outputs.workflow_name }}'; | |
| const runId = '${{ steps.failure_details.outputs.run_id }}'; | |
| const repoUrl = '${{ steps.failure_details.outputs.repo_url }}'; | |
| const runUrl = '${{ steps.failure_details.outputs.run_url }}'; | |
| const workflowYamlUrl = '${{ steps.failure_details.outputs.workflow_yaml_url }}'; | |
| const chainInfo = '${{ steps.failure_details.outputs.chain_info }}'; | |
| const errorSummary = '${{ steps.get_logs.outputs.error_summary }}'; | |
| const headSha = '${{ steps.failure_details.outputs.head_sha }}'; | |
| const headBranch = '${{ steps.failure_details.outputs.head_branch }}'; | |
| const failedAt = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; | |
| // Create comprehensive failure comment | |
| const commentBody = "❌ **PR Workflow Failure Notification**\n\n" + | |
| "- [Repo Cabinet (Source)](" + repoUrl + ")\n" + | |
| "- [Workflow Run](" + runUrl + ")\n" + | |
| "- [Workflow Definition](" + workflowYamlUrl + ")\n\n" + | |
| "### Workflow Chain:\n" + | |
| chainInfo + "\n\n" + | |
| "### Failure Details:\n" + | |
| "- **Branch**: `" + headBranch + "`\n" + | |
| "- **Commit**: `" + (headSha ? headSha.substring(0, 7) : 'unknown') + "`\n" + | |
| "- **Failed at**: " + failedAt + "\n\n" + | |
| errorSummary + "\n\n" + | |
| "### 🔧 Next Steps:\n" + | |
| "1. **Check Logs**: Click the [Workflow Run](" + runUrl + ") link above to view detailed error logs\n" + | |
| "2. **Review Code**: Check the failed commit for syntax errors, build issues, or test failures\n" + | |
| "3. **Fix Issues**: Address the errors shown in the logs\n" + | |
| "4. **Retry**: Push new commits or manually rerun the workflow\n\n" + | |
| "### 🔄 Quick Actions:\n" + | |
| "- 📄 [View detailed logs](" + runUrl + ")\n" + | |
| "- 🔄 [Rerun workflow](" + repoUrl + "/actions/workflows/" + workflowName.toLowerCase().replace(/\\s+/g, '-') + ".yml)\n" + | |
| "- 📝 [View workflow file](" + workflowYamlUrl + ")\n\n" + | |
| "---\n" + | |
| "💡 *This is an automated notification for workflow failures. Fix the issues above and the next workflow run should succeed.*"; | |
| try { | |
| // Check for existing failure notification comments to avoid duplicates | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| // Look for existing workflow failure comments for this specific run | |
| const existingComment = comments.find(comment => | |
| comment.body.includes('PR Workflow Failure Notification') && | |
| comment.body.includes(runId) | |
| ); | |
| if (existingComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: commentBody | |
| }); | |
| console.log(`✅ Updated existing failure notification comment for run ${runId} on PR #${prNumber}`); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: commentBody | |
| }); | |
| console.log(`✅ Posted new failure notification comment for run ${runId} on PR #${prNumber}`); | |
| } | |
| } catch (error) { | |
| console.error('Error posting failure comment:', error); | |
| // Don't fail the workflow if comment posting fails | |
| } |