-
Notifications
You must be signed in to change notification settings - Fork 53
500 lines (415 loc) · 18.9 KB
/
auto-issue-fix.yaml
File metadata and controls
500 lines (415 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
name: 🤖 Auto Issue Fix Pipeline
# Multi-agent pipeline that:
# 1. Scans and triages recent GitHub issues (issue-scanner agent)
# 2. Fixes the selected issue and pushes a fix branch (issue-fixer agent)
# 3. Verifies the fix branch against the DTS emulator (pr-verification agent)
#
# Agents are chained sequentially via file-based handoff:
# issue-scanner → /tmp/selected-issue.json → issue-fixer → /tmp/fix-branch-info.json → pr-verification
#
# Note: GitHub Copilot CLI agents do not support built-in handoffs in CI.
# Chaining is achieved by running agents as sequential workflow steps and
# injecting the previous agent's output into the next agent's prompt.
#
# The fixer agent pushes a branch (not a PR) because the workflow only has
# pull-requests: read permission. A human opens the PR from the branch.
on:
# Run every day at 09:00 UTC
schedule:
- cron: "0 9 * * *"
# Allow manual trigger for testing
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: read
jobs:
auto-issue-fix:
runs-on: ubuntu-latest
timeout-minutes: 150
env:
DOTNET_VER_6: "6.0.x"
DOTNET_VER_8: "8.0.x"
DOTNET_VER_10: "10.0.x"
SOLUTION: "Microsoft.DurableTask.sln"
steps:
# ─── Setup ───────────────────────────────────────────────────────
- name: 📥 Checkout code (full history for analysis)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⚙️ Setup .NET 6.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VER_6 }}
- name: ⚙️ Setup .NET 8.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VER_8 }}
- name: ⚙️ Setup .NET 10.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VER_10 }}
- name: ⚙️ Setup .NET from global.json
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: 🔨 Restore and build
run: |
dotnet restore $SOLUTION
dotnet build $SOLUTION --configuration Release --no-restore
- name: 🧪 Run tests (non-blocking baseline before analysis)
continue-on-error: true
run: |
dotnet test $SOLUTION --configuration Release --no-build --verbosity normal
# ─── Deduplication Context ───────────────────────────────────────
- name: 🔍 Collect existing work to avoid duplicates
id: dedup
run: |
echo "Fetching open PRs and issues with copilot-finds label..."
# Get open PRs with copilot-finds label (include file paths for targeted dedup)
OPEN_PRS=$(gh pr list \
--label "copilot-finds" \
--state open \
--limit 50 \
--json title,url,headRefName,files \
--jq '[.[] | {title: .title, url: .url, branch: .headRefName, files: [.files[].path]}]' \
2>/dev/null || echo "[]")
# Get open issues with copilot-finds label
OPEN_ISSUES=$(gh issue list \
--label "copilot-finds" \
--state open \
--limit 50 \
--json title,url \
--jq '[.[] | {title: .title, url: .url}]' \
2>/dev/null || echo "[]")
# Get recently merged PRs (last 14 days) — title/url only to avoid prompt bloat
RECENT_MERGED=$(gh pr list \
--label "copilot-finds" \
--state merged \
--limit 50 \
--json title,url,mergedAt \
--jq '[.[] | select((.mergedAt | fromdateiso8601) > (now - 14*86400)) | {title: .title, url: .url}]' \
2>/dev/null || echo "[]")
# Write dedup context
cat <<DEDUP_EOF > /tmp/exclusion-context.txt
=== EXISTING WORK (DO NOT DUPLICATE) ===
## Open PRs with copilot-finds label:
$OPEN_PRS
## Open issues with copilot-finds label:
$OPEN_ISSUES
## Recently merged copilot-finds PRs (last 14 days):
$RECENT_MERGED
=== END EXISTING WORK ===
DEDUP_EOF
echo "Dedup context collected."
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
# ─── Labels ─────────────────────────────────────────────────────
- name: 🏷️ Ensure required labels exist
run: |
set -euo pipefail
ensure_label() {
local name="$1"
local description="$2"
local color="$3"
if gh label list --limit 200 --json name --jq '.[].name' | grep -Fxq "$name"; then
echo "Label '$name' already exists; skipping."
else
echo "Creating label '$name'."
gh label create "$name" --description "$description" --color "$color"
fi
}
# Pipeline labels
ensure_label "copilot-finds" \
"Findings from automated code review agents" "7057ff"
ensure_label "pending-verification" \
"PR awaiting automated verification" "fbca04"
ensure_label "sample-verification-added" \
"PR has been verified by the PR verification agent" "0e8a16"
# Triage labels (used by issue-scanner agent)
ensure_label "triage/actionable" \
"Issue is ready for automated fix — clear scope, no blockers" "1d76db"
ensure_label "triage/needs-human-verification" \
"Requires human judgment or domain expertise to verify" "c5def5"
ensure_label "triage/known-blocker" \
"Has a known dependency or blocker preventing fix" "e4e669"
ensure_label "triage/requires-redesign" \
"Needs architectural changes or design discussion" "d4c5f9"
ensure_label "triage/needs-info" \
"Missing reproduction steps or unclear description" "d93f0b"
ensure_label "triage/already-fixed" \
"Already resolved by a merged PR or closed" "bfdadc"
ensure_label "triage/too-large" \
"Scope too large for a single automated fix" "f9d0c4"
ensure_label "triage/external-dependency" \
"Depends on changes in another repo or service" "c2e0c6"
ensure_label "triage/duplicate" \
"Duplicate of another issue" "cfd3d7"
ensure_label "triage/feature-request" \
"Feature request, not a bug fix" "a2eeef"
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
# ─── Agent 1: Issue Scanner ─────────────────────────────────────
- name: 🤖 Install GitHub Copilot CLI
run: npm install -g @github/copilot
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
- name: 🔍 Agent 1 — Issue Scanner
id: issue_scanner
run: |
EXCLUSION_CONTEXT=$(cat /tmp/exclusion-context.txt)
AGENT_PROMPT=$(cat .github/agents/issue-scanner.agent.md)
FULL_PROMPT=$(cat <<PROMPT_EOF
$AGENT_PROMPT
---
## Pre-loaded Deduplication Context
The following items are already tracked. DO NOT select issues that overlap
with any of these:
$EXCLUSION_CONTEXT
---
## Execution Instructions
You are running in CI. Today's date is $(date +%Y-%m-%d).
Repository: ${{ github.repository }}
Execute the full workflow described above:
1. Fetch the 20 most recent GitHub issues
2. Triage and classify each one
3. Select the single best actionable issue (if any)
4. Write the handoff context to /tmp/selected-issue.json
Remember:
- Be conservative — only select issues with high confidence
- Write the handoff file to /tmp/selected-issue.json (MANDATORY)
- If no actionable issue is found, write {"found": false, ...} and stop
PROMPT_EOF
)
EXIT_CODE=0
timeout --foreground --signal=TERM --kill-after=30s 1200s \
copilot \
--prompt "$FULL_PROMPT" \
--model "claude-opus-4.6" \
--allow-all-tools \
--allow-all-paths \
< /dev/null 2>&1 || EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
echo "::warning::Issue scanner agent timed out after 20 minutes"
fi
# Check if an issue was found
if [ -f /tmp/selected-issue.json ]; then
FOUND=$(cat /tmp/selected-issue.json | jq -r '.found // false')
echo "issue_found=$FOUND" >> $GITHUB_OUTPUT
if [ "$FOUND" = "true" ]; then
ISSUE_NUM=$(cat /tmp/selected-issue.json | jq -r '.issueNumber')
echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT
echo "Issue #$ISSUE_NUM selected for fixing."
else
echo "No actionable issue found. Pipeline will stop."
fi
else
echo "issue_found=false" >> $GITHUB_OUTPUT
echo "::warning::Handoff file not created — no issue found."
fi
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
CI: "true"
NO_COLOR: "1"
TERM: "dumb"
# ─── Agent 2: Issue Fixer ───────────────────────────────────────
- name: 🔧 Agent 2 — Issue Fixer
id: issue_fixer
if: steps.issue_scanner.outputs.issue_found == 'true'
run: |
ISSUE_CONTEXT=$(cat /tmp/selected-issue.json)
AGENT_PROMPT=$(cat .github/agents/issue-fixer.agent.md)
FULL_PROMPT=$(cat <<PROMPT_EOF
$AGENT_PROMPT
---
## Injected Issue Context (from Issue Scanner Agent)
The issue-scanner agent has selected the following issue for you to fix:
\`\`\`json
$ISSUE_CONTEXT
\`\`\`
---
## Execution Instructions
You are running in CI. Today's date is $(date +%Y-%m-%d).
Repository: ${{ github.repository }}
Execute the full workflow described above:
1. Read the injected issue context above
2. Deep-analyze the codebase to understand the problem
3. Implement the fix following ALL repository conventions
4. Add comprehensive unit tests (and integration tests if applicable)
5. Run the full test suite: dotnet test Microsoft.DurableTask.sln --configuration Release
6. Push a fix branch and post a comment on the issue with the branch link
7. Write the handoff context to /tmp/fix-branch-info.json
IMPORTANT: Do NOT open a PR. You do not have permission to create PRs.
Instead, push a branch and comment on the issue so a human can open the PR.
Remember:
- Follow all C# conventions from .github/copilot-instructions.md
- Copyright headers, XML docs, this., Async suffix, sealed private classes
- All tests must pass
- Write the handoff file to /tmp/fix-branch-info.json (MANDATORY)
PROMPT_EOF
)
EXIT_CODE=0
timeout --foreground --signal=TERM --kill-after=30s 2400s \
copilot \
--prompt "$FULL_PROMPT" \
--model "claude-opus-4.6" \
--allow-all-tools \
--allow-all-paths \
< /dev/null 2>&1 || EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
echo "::warning::Issue fixer agent timed out after 40 minutes"
fi
# Check if a fix branch was pushed
if [ -f /tmp/fix-branch-info.json ]; then
CREATED=$(cat /tmp/fix-branch-info.json | jq -r '.created // false')
echo "branch_created=$CREATED" >> $GITHUB_OUTPUT
if [ "$CREATED" = "true" ]; then
BRANCH_NAME=$(cat /tmp/fix-branch-info.json | jq -r '.branchName')
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "Fix branch '$BRANCH_NAME' pushed successfully."
else
echo "No fix branch was pushed. Pipeline will stop before verification."
fi
else
echo "branch_created=false" >> $GITHUB_OUTPUT
echo "::warning::Handoff file not created — no fix branch pushed."
fi
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
CI: "true"
NO_COLOR: "1"
TERM: "dumb"
# ─── Agent 3: Branch Verification ──────────────────────────────
- name: 🐳 Start DTS Emulator
if: steps.issue_fixer.outputs.branch_created == 'true'
run: |
docker run --name dts-emulator -d --rm -p 4001:8080 \
mcr.microsoft.com/dts/dts-emulator:latest
echo "Waiting for emulator to be ready..."
for i in $(seq 1 30); do
if nc -z localhost 4001 2>/dev/null; then
echo "Emulator is ready!"
break
fi
if [ "$i" -eq 30 ]; then
echo "Emulator failed to start within 30 seconds"
exit 1
fi
sleep 1
done
- name: 🔎 Agent 3 — Branch Verification
if: steps.issue_fixer.outputs.branch_created == 'true'
run: |
BRANCH_CONTEXT=$(cat /tmp/fix-branch-info.json)
AGENT_PROMPT=$(cat .github/agents/pr-verification.agent.md)
FULL_PROMPT=$(cat <<PROMPT_EOF
$AGENT_PROMPT
---
## Injected Branch Context (from Issue Fixer Agent)
The issue-fixer agent has pushed the following fix branch for you to verify:
\`\`\`json
$BRANCH_CONTEXT
\`\`\`
---
## Execution Instructions
You are running in CI. Today's date is $(date +%Y-%m-%d).
Repository: ${{ github.repository }}
The DTS emulator is running at localhost:4001.
Execute the full workflow described above:
1. Read the injected branch context above
2. Understand the fix from the branch diff
3. Extract the verification scenario
4. Checkout the fix branch and rebuild
5. Create a standalone C# verification sample
6. Run it against the emulator
7. Post verification results to the linked issue
Remember:
- DTS_ENDPOINT=localhost:4001
- DTS_TASKHUB=default
- Always checkout the fix branch before building/running
- Retry up to 2 times on failure
- Maximum timeout per verification: 5 minutes
PROMPT_EOF
)
EXIT_CODE=0
timeout --foreground --signal=TERM --kill-after=30s 1800s \
copilot \
--prompt "$FULL_PROMPT" \
--model "claude-opus-4.6" \
--allow-all-tools \
--allow-all-paths \
< /dev/null 2>&1 || EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
echo "::warning::Branch verification agent timed out after 30 minutes"
elif [ $EXIT_CODE -ne 0 ]; then
echo "::warning::Branch verification agent exited with code $EXIT_CODE"
fi
echo "Branch verification agent completed."
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
DTS_ENDPOINT: localhost:4001
DTS_TASKHUB: default
CI: "true"
NO_COLOR: "1"
TERM: "dumb"
- name: 🧹 Stop DTS Emulator
if: always()
run: docker stop dts-emulator 2>/dev/null || true
# ─── Summary ────────────────────────────────────────────────────
- name: 📊 Pipeline Summary
if: always()
run: |
echo "## 🤖 Auto Issue Fix Pipeline Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Date:** $(date +%Y-%m-%d)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Agent 1 results
echo "### Agent 1: Issue Scanner" >> $GITHUB_STEP_SUMMARY
if [ -f /tmp/selected-issue.json ]; then
FOUND=$(cat /tmp/selected-issue.json | jq -r '.found // false')
if [ "$FOUND" = "true" ]; then
ISSUE_NUM=$(cat /tmp/selected-issue.json | jq -r '.issueNumber')
ISSUE_TITLE=$(cat /tmp/selected-issue.json | jq -r '.issueTitle')
echo "- ✅ Selected issue: #$ISSUE_NUM — $ISSUE_TITLE" >> $GITHUB_STEP_SUMMARY
else
REASON=$(cat /tmp/selected-issue.json | jq -r '.reason // "Unknown"')
echo "- ⏭️ No actionable issue found: $REASON" >> $GITHUB_STEP_SUMMARY
fi
else
echo "- ❌ Handoff file not created" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Agent 2 results
echo "### Agent 2: Issue Fixer" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.issue_scanner.outputs.issue_found }}" != "true" ]; then
echo "- ⏭️ Skipped (no issue selected)" >> $GITHUB_STEP_SUMMARY
elif [ -f /tmp/fix-branch-info.json ]; then
CREATED=$(cat /tmp/fix-branch-info.json | jq -r '.created // false')
if [ "$CREATED" = "true" ]; then
BRANCH_NAME=$(cat /tmp/fix-branch-info.json | jq -r '.branchName')
BRANCH_URL=$(cat /tmp/fix-branch-info.json | jq -r '.branchUrl')
echo "- ✅ Fix branch pushed: [$BRANCH_NAME]($BRANCH_URL)" >> $GITHUB_STEP_SUMMARY
else
echo "- ⏭️ No fix branch pushed" >> $GITHUB_STEP_SUMMARY
fi
else
echo "- ❌ Fixer agent ran but handoff file was not created (agent may have crashed or timed out)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Agent 3 results
echo "### Agent 3: Branch Verification" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.issue_fixer.outputs.branch_created }}" = "true" ]; then
echo "- ✅ Verification completed (check issue comments for results)" >> $GITHUB_STEP_SUMMARY
else
echo "- ⏭️ Skipped (no fix branch to verify)" >> $GITHUB_STEP_SUMMARY
fi
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}