-
Notifications
You must be signed in to change notification settings - Fork 673
Report Codespaces prebuild failures in GitHub issues #8191
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
39070c4
chore: add workflow failure issue reporting
Copilot a0b0f88
chore: refine workflow failure issue details
Copilot 28cc6ee
chore: manage workflow failure issue lifecycle
Copilot 43f306d
chore: harden workflow issue deduplication
Copilot 18a9a13
chore: finalize workflow issue lifecycle
Copilot 232677f
chore: guard workflow run lookup
Copilot 44672e3
chore: preserve canonical workflow issue
Copilot 3dd27f1
chore: split workflow issue lifecycle jobs
Copilot 6b9c990
Update failure notification message in workflow
joshblack 7b2353a
chore: harden workflow failure issue handling
Copilot 7f254ad
chore: address workflow review feedback
Copilot 6398df3
chore: polish workflow failure issue script
Copilot dad1c76
chore: clarify workflow issue warnings
Copilot bb64f3e
chore: finalize workflow issue automation
Copilot d69e98e
chore: harden workflow failure issue formatting
Copilot f8490ca
chore: finalize workflow issue escaping
Copilot 8fa314d
Apply suggestion from @jonrohan
joshblack fefcc2f
Merge branch 'main' into copilot/add-workflow-failure-issue
joshblack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| name: Report workflow failures | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: | ||
| - Codespaces Prebuilds | ||
| types: | ||
| - completed | ||
|
|
||
| permissions: {} | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.workflow_run.workflow_id }} | ||
|
|
||
| jobs: | ||
| create-or-update-issue: | ||
| if: ${{ github.event.workflow_run.conclusion == 'failure' }} | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| actions: read | ||
| issues: write | ||
| steps: | ||
| - name: Create or update issue | ||
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | ||
| with: | ||
| script: | | ||
| const workflowRun = context.payload.workflow_run; | ||
| const latestRuns = await github.rest.actions.listWorkflowRuns({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| workflow_id: workflowRun.workflow_id, | ||
| status: 'completed', | ||
| per_page: 1, | ||
| }); | ||
| const latestRun = latestRuns.data.workflow_runs[0]; | ||
|
|
||
| if ( | ||
| latestRun?.id !== workflowRun.id || | ||
| latestRun?.run_attempt !== workflowRun.run_attempt | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const workflow = await github.rest.actions.getWorkflow({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| workflow_id: workflowRun.workflow_id, | ||
| }); | ||
| const title = `Investigate ${workflow.data.name} workflow failure`; | ||
| const issueMarkerText = `workflow-failure:${workflowRun.workflow_id}`; | ||
| const issueMarker = `<!-- ${issueMarkerText} -->`; | ||
| const issues = await github.paginate(github.rest.search.issuesAndPullRequests, { | ||
| q: [ | ||
| `repo:${context.repo.owner}/${context.repo.repo}`, | ||
| 'is:issue', | ||
| 'is:open', | ||
| `"${issueMarkerText}"`, | ||
| 'in:body', | ||
| ].join(' '), | ||
| per_page: 100, | ||
| }); | ||
| const failureIssues = issues | ||
| .filter( | ||
| (issue) => | ||
| !issue.pull_request && issue.body?.includes(issueMarker), | ||
| ) | ||
| .sort((a, b) => a.number - b.number); | ||
|
|
||
| const jobs = await github.paginate( | ||
| github.rest.actions.listJobsForWorkflowRun, | ||
| { | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| run_id: workflowRun.id, | ||
| filter: 'latest', | ||
| per_page: 100, | ||
| }, | ||
| ); | ||
| const failedJobs = jobs.filter((job) => job.conclusion === 'failure'); | ||
| const escapeHtml = (text) => | ||
| text | ||
| .replaceAll('&', '&') | ||
| .replaceAll('<', '<') | ||
| .replaceAll('>', '>') | ||
| .replaceAll('"', '"') | ||
| .replaceAll("'", '''); | ||
| const failedJobListItems = failedJobs.map( | ||
| (job) => | ||
| `- <a href="${job.html_url}">${escapeHtml(job.name)}</a>`, | ||
| ); | ||
|
|
||
| if (failedJobListItems.length === 0) { | ||
| failedJobListItems.push(`- <a href="${workflowRun.html_url}">Failed run</a>`); | ||
| } | ||
|
|
||
| const body = [ | ||
| issueMarker, | ||
| 'The following jobs are failing in this workflow:', | ||
| '', | ||
| ...failedJobListItems, | ||
| '', | ||
| 'cc @github/primer-engineering to investigate and fix', | ||
| ].join('\n'); | ||
|
|
||
| const issue = { | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title, | ||
| body, | ||
| }; | ||
|
|
||
| let issueNumber; | ||
|
|
||
| if (failureIssues.length > 0) { | ||
| await github.rest.issues.update({ | ||
| ...issue, | ||
| issue_number: failureIssues[0].number, | ||
| }); | ||
| issueNumber = failureIssues[0].number; | ||
| await Promise.all( | ||
| failureIssues.slice(1).map((duplicate) => | ||
| github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: duplicate.number, | ||
| state: 'closed', | ||
| state_reason: 'not_planned', | ||
| }), | ||
| ), | ||
| ); | ||
| } else { | ||
| const createdIssue = await github.rest.issues.create(issue); | ||
| issueNumber = createdIssue.data.number; | ||
| } | ||
|
|
||
| try { | ||
| await github.rest.issues.addAssignees({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issueNumber, | ||
| assignees: ['copilot'], | ||
| }); | ||
| } catch (error) { | ||
| core.warning( | ||
| `Failed to assign @copilot to issue #${issueNumber}. This is expected if Copilot assignment is not enabled for this repository. Error: ${error?.message ?? String(error)}`, | ||
| ); | ||
| } | ||
|
|
||
| close-issue: | ||
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| actions: read | ||
| issues: write | ||
| steps: | ||
| - name: Close issue | ||
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | ||
| with: | ||
| script: | | ||
| const workflowRun = context.payload.workflow_run; | ||
| const latestRuns = await github.rest.actions.listWorkflowRuns({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| workflow_id: workflowRun.workflow_id, | ||
| status: 'completed', | ||
| per_page: 1, | ||
| }); | ||
|
joshblack marked this conversation as resolved.
|
||
| const latestRun = latestRuns.data.workflow_runs[0]; | ||
|
|
||
| if ( | ||
| latestRun?.id !== workflowRun.id || | ||
| latestRun?.run_attempt !== workflowRun.run_attempt | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const issueMarkerText = `workflow-failure:${workflowRun.workflow_id}`; | ||
| const issueMarker = `<!-- ${issueMarkerText} -->`; | ||
| const issues = await github.paginate(github.rest.search.issuesAndPullRequests, { | ||
| q: [ | ||
| `repo:${context.repo.owner}/${context.repo.repo}`, | ||
| 'is:issue', | ||
| 'is:open', | ||
| `"${issueMarkerText}"`, | ||
| 'in:body', | ||
| ].join(' '), | ||
| per_page: 100, | ||
| }); | ||
| const failureIssues = issues.filter( | ||
| (issue) => | ||
| !issue.pull_request && issue.body?.includes(issueMarker), | ||
| ); | ||
|
|
||
| for (const issue of failureIssues) { | ||
| await github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| state: 'closed', | ||
| state_reason: 'completed', | ||
| }); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.