-
Notifications
You must be signed in to change notification settings - Fork 19
166 lines (141 loc) · 6.88 KB
/
bug-triage-cron.yml
File metadata and controls
166 lines (141 loc) · 6.88 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
name: Bug Triage (Cron)
on:
schedule:
- cron: '0 8 * * 1-5' # Weekdays at 08:00 UTC
workflow_dispatch:
concurrency:
group: bug-triage
cancel-in-progress: true
permissions:
contents: read
issues: write
jobs:
triage:
name: Triage Bug Issues
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3
with:
app-id: ${{ secrets.TOOLHIVE_STUDIO_CI_APP_ID }}
private-key: ${{ secrets.TOOLHIVE_STUDIO_CI_APP_KEY }}
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.repository.default_branch }}
- name: Fetch candidate bug issues
id: candidates
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Get open Bug issues without auto-fix or auto-fix-skip labels
ISSUES=$(gh issue list \
--label "Bug" \
--state open \
--limit 50 \
--json number,title,body,labels,comments \
--jq '[.[] | select(
(.labels | map(.name) | (contains(["auto-fix"]) or contains(["auto-fix-skip"])) | not)
and (.comments == 0)
)]')
COUNT=$(echo "$ISSUES" | jq 'length')
echo "::notice::Found $COUNT candidate bug issues"
if [ "$COUNT" -eq "0" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
echo "$ISSUES" > candidate-issues.json
fi
- name: Filter issues with existing PRs
id: filter
if: steps.candidates.outputs.skip != 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Batch-fetch all open fix/auto-* PRs in a single API call
OPEN_BRANCHES=$(gh pr list --state open --limit 200 --json headRefName \
--jq '[.[].headRefName | select(startswith("fix/auto-"))]')
FILTERED="[]"
for NUM in $(jq -r '.[].number' candidate-issues.json); do
# Skip if a fix PR already exists
BRANCH="fix/auto-${NUM}"
if echo "$OPEN_BRANCHES" | jq -e "index(\"$BRANCH\")" > /dev/null 2>&1; then
echo "::notice::Issue #${NUM}: skipped — open PR exists"
continue
fi
# Skip if a cross-repo issue/PR was closed or merged (fix lives in another repo)
CROSS_REF_CLOSED=$(gh api "repos/${{ github.repository }}/issues/${NUM}/timeline" --paginate 2>/dev/null \
| jq '[.[] | select(.event == "cross-referenced" and .source.issue.state == "closed")] | length' 2>/dev/null || echo "0")
if [ "$CROSS_REF_CLOSED" -gt "0" ]; then
echo "::notice::Issue #${NUM}: skipped — cross-referenced issue/PR closed in another repo"
continue
fi
ISSUE=$(jq ".[] | select(.number == $NUM)" candidate-issues.json)
FILTERED=$(echo "$FILTERED" | jq ". + [$ISSUE]")
done
echo "$FILTERED" > filtered-issues.json
COUNT=$(echo "$FILTERED" | jq 'length')
echo "::notice::$COUNT issues remain after PR dedup"
if [ "$COUNT" -eq "0" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Prepare issue summaries
if: steps.candidates.outputs.skip != 'true' && steps.filter.outputs.skip != 'true'
run: |
jq -r '.[:10][] | "--- Issue #\(.number): \(.title)\n\(.body)\n"' filtered-issues.json > issues-for-triage.md
- name: Evaluate issues with Claude (Sonnet)
if: steps.candidates.outputs.skip != 'true' && steps.filter.outputs.skip != 'true'
uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Read issues-for-triage.md. These are open bug reports for a React + Electron desktop app.
Analyze each bug and decide if it is suitable for AUTOMATED fixing by an AI agent that:
- Can only write and run unit tests (Vitest + Testing Library + MSW)
- Cannot launch the Electron app or do visual testing
- Cannot test IPC, native modules, or platform-specific behavior
A bug IS suitable if:
- Has a clear error message or stack trace
- Mentions a specific component, page, or feature by name
- Is a UI bug (wrong text, missing state, incorrect condition, wrong rendering)
- Has clear steps to reproduce
- Can be reproduced in a jsdom unit test
A bug is NOT suitable if:
- Mentions IPC, Electron main process, or platform-specific behavior
- Involves timing, race conditions, or flaky behavior
- Is vague or lacks reproduction steps
- Requires manual testing or visual verification
- Involves packaging, code signing, or native modules
- Requires launching the app or E2E testing
- References a fix already merged or in progress in another repository (e.g., stacklok/toolhive). If the bug is caused by an upstream dependency and the fix lives outside this repo, it is NOT suitable.
- Mentions that the root cause is in a backend, CLI, or server component outside this React/Electron codebase
Search the codebase with Grep/Glob to verify the mentioned components/features exist.
Output ONLY a JSON array, one object per issue:
[{"issue": <number>, "suitable": true/false, "reason": "<one line>"}]
Write this JSON array to a file called triage-results.json using the Write tool. Nothing else.
claude_args: >-
--model sonnet
--max-turns 20
--allowedTools "Read,Grep,Glob,Write"
- name: Apply auto-fix label (max 3 per run)
if: steps.candidates.outputs.skip != 'true' && steps.filter.outputs.skip != 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if [ ! -f triage-results.json ]; then
echo "::warning::triage-results.json not found — skipping label assignment"
exit 0
fi
SUITABLE=$(jq -r '[.[] | select(.suitable == true)] | .[:3]' triage-results.json)
COUNT=$(echo "$SUITABLE" | jq 'length')
echo "::notice::Labeling $COUNT issues as auto-fix"
echo "$SUITABLE" | jq -c '.[]' | while IFS= read -r ROW; do
NUM=$(echo "$ROW" | jq -r '.issue')
REASON=$(echo "$ROW" | jq -r '.reason')
echo "::notice::Issue #${NUM}: ${REASON}"
gh issue edit "$NUM" --add-label "auto-fix"
done