Skip to content

Commit 0b7a00b

Browse files
authored
Add auto-analyze failures workflow
This workflow automatically analyzes build failures, logs details, and creates remediation issues if necessary.
1 parent 5d7f061 commit 0b7a00b

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
name: Auto Analyze Build Failures
2+
3+
on:
4+
workflow_run:
5+
workflows: ["*"]
6+
types: [completed]
7+
8+
permissions:
9+
contents: read
10+
actions: write
11+
issues: write
12+
pull-requests: read
13+
models: read
14+
15+
jobs:
16+
analyze-failure:
17+
runs-on: ubuntu-latest
18+
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.name != 'Auto Analyze Build Failures' }}
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Log job details
25+
run: |
26+
echo "Workflow Name: ${{ github.workflow }}"
27+
echo "Run ID: ${{ github.run_id }}"
28+
echo "Job Name: ${{ github.job }}"
29+
echo "Repository: ${{ github.repository }}"
30+
echo "Actor: ${{ github.actor }}"
31+
32+
- name: Analyze build failure
33+
id: analyze
34+
uses: actions/ai-inference@v1
35+
with:
36+
prompt-file: '.github/models/failed-run-analyze.prompt.yml'
37+
enable-github-mcp: true
38+
token: ${{ secrets.GITHUB_TOKEN }}
39+
github-mcp-token: ${{ secrets.AUTO_REMEDIATION_PAT }}
40+
max-tokens: 10000
41+
input: |
42+
repo: ${{ github.event.repository.name }}
43+
owner: ${{ github.event.repository.owner.login }}
44+
workflow_run_id: ${{ github.event.workflow_run.id }}
45+
46+
- name: Parse results
47+
id: parse
48+
uses: actions/github-script@v7
49+
env:
50+
RESPONSE_JSON: ${{ steps.analyze.outputs.response }}
51+
with:
52+
script: |
53+
const responseString = process.env.RESPONSE_JSON;
54+
core.info(`Raw response string: ${responseString}`)
55+
56+
if (!responseString || responseString === '') {
57+
core.setFailed('No response received from analysis step')
58+
return
59+
}
60+
61+
try {
62+
const responseJSON = JSON.parse(responseString)
63+
core.info(`Parsed analysis result: ${JSON.stringify(responseJSON, null, 2)}`)
64+
65+
// Set individual outputs for easier access in subsequent steps
66+
core.setOutput('category', responseJSON.category || '')
67+
core.setOutput('summary', responseJSON.summary || '')
68+
core.setOutput('plan', responseJSON.plan || '')
69+
core.setOutput('transient', responseJSON.transient || 'false')
70+
71+
// Also set the full response for backward compatibility
72+
core.setOutput('response', responseJSON)
73+
} catch (error) {
74+
core.setFailed(`Failed to parse JSON response: ${error.message}`)
75+
core.info(`Problematic response string: ${responseString}`)
76+
}
77+
78+
- name: Log parse values
79+
env:
80+
CATEGORY: ${{ steps.parse.outputs.category }}
81+
SUMMARY: ${{ steps.parse.outputs.summary }}
82+
PLAN: ${{ steps.parse.outputs.plan }}
83+
TRANSIENT: ${{ steps.parse.outputs.transient }}
84+
run: |
85+
printf 'Category: %s\n' "$CATEGORY"
86+
printf 'Summary: %s\n' "$SUMMARY"
87+
printf 'Plan: %s\n' "$PLAN"
88+
printf 'Transient: %s\n' "$TRANSIENT"
89+
90+
- name: Check for existing remediation issue
91+
if: ${{ steps.parse.outputs.transient == 'false' }}
92+
id: check-issue
93+
run: |
94+
workflow_name="${{ github.event.workflow_run.name }}"
95+
96+
# Search for existing open issues with the workflow label
97+
existing_issue=$(gh issue list \
98+
--repo "${{ github.repository }}" \
99+
--state open \
100+
--label "workflow:$workflow_name" \
101+
--label "auto-remediation" \
102+
--json number \
103+
--jq '.[0].number')
104+
105+
echo "existing_issue=$existing_issue" >> $GITHUB_OUTPUT
106+
env:
107+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108+
109+
- name: Create remediation issue
110+
id: create-issue
111+
if: ${{ steps.parse.outputs.transient == 'false' }}
112+
run: |
113+
workflow_name="${{ github.event.workflow_run.name }}"
114+
workflow_url="${{ github.event.workflow_run.html_url }}"
115+
category="${{ steps.parse.outputs.category }}"
116+
117+
# Check if we should create an issue or skip due to existing issue
118+
existing_issue="${{ steps.check-issue.outputs.existing_issue }}"
119+
if [[ -n "$existing_issue" ]]; then
120+
echo "Skipping issue creation - existing issue #$existing_issue found"
121+
exit 0
122+
fi
123+
124+
# Add note if this was a repeated transient failure
125+
repeat_note=""
126+
if [[ "${{ steps.check-previous.outputs.repeat-transient }}" == "true" ]]; then
127+
repeat_note="**Note:** This was initially classified as a transient failure but occurred in consecutive runs, indicating a persistent issue."
128+
fi
129+
130+
issue_body=$(cat << EOF
131+
## Build Failure Analysis
132+
133+
**Workflow:** [$workflow_name]($workflow_url)
134+
**Run ID:** ${{ github.event.workflow_run.id }}
135+
**Category:** $category
136+
**Branch:** ${{ github.event.workflow_run.head_branch }}
137+
**Commit:** ${{ github.event.workflow_run.head_sha }}
138+
139+
$repeat_note
140+
141+
### Summary
142+
${{ steps.parse.outputs.summary }}
143+
144+
### Remediation Plan
145+
${{ steps.parse.outputs.plan }}
146+
147+
### Links
148+
- [Failed Workflow Run]($workflow_url)
149+
- [Repository](${{ github.event.repository.html_url }})
150+
151+
---
152+
*This issue was automatically created by the build failure analysis system.*
153+
EOF
154+
)
155+
156+
# Ensure required labels exist
157+
echo "Creating labels if they don't exist..."
158+
159+
# Create auto-remediation label
160+
gh label create "auto-remediation" \
161+
--description "Issues automatically created by build failure analysis" \
162+
--color "FF6B6B" \
163+
--repo "${{ github.repository }}" || echo "Label 'auto-remediation' already exists or creation failed"
164+
165+
# Create workflow-specific label
166+
gh label create "workflow:$workflow_name" \
167+
--description "Issues related to $workflow_name workflow" \
168+
--color "0052CC" \
169+
--repo "${{ github.repository }}" || echo "Label 'workflow:$workflow_name' already exists or creation failed"
170+
171+
# Create category-specific label
172+
gh label create "category:$category" \
173+
--description "Issues categorized as $category" \
174+
--color "7057ff" \
175+
--repo "${{ github.repository }}" || echo "Label 'category:$category' already exists or creation failed"
176+
177+
# Create new issue
178+
echo "Creating new remediation issue"
179+
issue_url=$(gh issue create \
180+
--repo "${{ github.repository }}" \
181+
--title "🔧 Auto-Remediation: $workflow_name Build Failure" \
182+
--body "$issue_body" \
183+
--label "auto-remediation" \
184+
--label "workflow:$workflow_name" \
185+
--label "category:$category")
186+
187+
# Extract issue number from URL
188+
issue_number=$(echo "$issue_url" | sed 's/.*\/issues\///')
189+
echo "Created issue #$issue_number"
190+
echo "issue_number=$issue_number" >> $GITHUB_OUTPUT
191+
env:
192+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
193+
194+
- name: Assign issue to Copilot
195+
if: ${{ steps.parse.outputs.transient == 'false' && steps.create-issue.outputs.issue_number != '' }}
196+
run: |
197+
category="${{ steps.parse.outputs.category }}"
198+
issue_number="${{ steps.create-issue.outputs.issue_number }}"
199+
200+
# Only assign to Copilot for code-related issues
201+
if [[ "$category" == "code" || "$category" == "test" || "$category" == "config" ]]; then
202+
echo "Assigning issue #$issue_number to Copilot for code-related failure"
203+
204+
# First, check if Copilot is available in this repository
205+
copilot_query='query {
206+
repository(owner: "${{ github.event.repository.owner.login }}", name: "${{ github.event.repository.name }}") {
207+
suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 100) {
208+
nodes {
209+
login
210+
__typename
211+
... on Bot {
212+
id
213+
}
214+
}
215+
}
216+
}
217+
}'
218+
219+
copilot_response=$(gh api graphql -f query="$copilot_query")
220+
copilot_id=$(echo "$copilot_response" | jq -r '.data.repository.suggestedActors.nodes[] | select(.login == "copilot-swe-agent") | .id')
221+
222+
if [[ -n "$copilot_id" && "$copilot_id" != "null" ]]; then
223+
echo "Found Copilot agent ID: $copilot_id"
224+
225+
# Get the issue GraphQL ID
226+
issue_query='query {
227+
repository(owner: "${{ github.event.repository.owner.login }}", name: "${{ github.event.repository.name }}") {
228+
issue(number: '$issue_number') {
229+
id
230+
title
231+
}
232+
}
233+
}'
234+
235+
issue_response=$(gh api graphql -f query="$issue_query")
236+
issue_id=$(echo "$issue_response" | jq -r '.data.repository.issue.id')
237+
238+
if [[ -n "$issue_id" && "$issue_id" != "null" ]]; then
239+
echo "Found issue ID: $issue_id"
240+
241+
# Assign the issue to Copilot
242+
assign_mutation='mutation {
243+
replaceActorsForAssignable(input: {assignableId: "'$issue_id'", actorIds: ["'$copilot_id'"]}) {
244+
assignable {
245+
... on Issue {
246+
id
247+
title
248+
assignees(first: 10) {
249+
nodes {
250+
login
251+
}
252+
}
253+
}
254+
}
255+
}
256+
}'
257+
258+
assign_response=$(gh api graphql -f query="$assign_mutation")
259+
echo "Assignment response: $assign_response"
260+
261+
# Check if assignment was successful
262+
assignees=$(echo "$assign_response" | jq -r '.data.replaceActorsForAssignable.assignable.assignees.nodes[].login')
263+
if echo "$assignees" | grep -q "Copilot"; then
264+
echo "✅ Successfully assigned issue #$issue_number to Copilot"
265+
else
266+
echo "❌ Failed to assign issue to Copilot"
267+
echo "Response: $assign_response"
268+
fi
269+
else
270+
echo "❌ Could not find issue GraphQL ID"
271+
fi
272+
else
273+
echo "⚠️ Copilot coding agent not available in this repository"
274+
echo "Available actors: $(echo "$copilot_response" | jq -r '.data.repository.suggestedActors.nodes[].login')"
275+
fi
276+
else
277+
echo "ℹ️ Issue category '$category' does not require Copilot assignment"
278+
fi
279+
env:
280+
GH_TOKEN: ${{ secrets.AUTO_REMEDIATION_PAT }}
281+
282+
- name: Summary
283+
env:
284+
TRANSIENT: ${{ steps.parse.outputs.transient }}
285+
CATEGORY: ${{ steps.parse.outputs.category }}
286+
SUMMARY: ${{ steps.parse.outputs.summary }}
287+
ISSUE_NUMBER: ${{ steps.create-issue.outputs.issue_number }}
288+
run: |
289+
if [[ "$TRANSIENT" == "true" ]]; then
290+
echo "✅ Transient failure detected - skipped issue creation"
291+
else
292+
echo "🔧 Non-transient failure - remediation issue created"
293+
printf 'Category: %s\n' "$CATEGORY"
294+
printf 'Summary: %s\n' "$SUMMARY"
295+
printf 'ID: %s\n' "$ISSUE_NUMBER"
296+
fi

0 commit comments

Comments
 (0)