Skip to content

Commit 65cd337

Browse files
feat: add automated issue triage via OpenCode and AI Gateway (#12517)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent e438f50 commit 65cd337

File tree

3 files changed

+337
-0
lines changed

3 files changed

+337
-0
lines changed

.github/opencode.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "https://opencode.ai/config.json",
3+
"disabled_providers": ["opencode"],
4+
"enabled_providers": ["cloudflare-ai-gateway"],
5+
"provider": {
6+
"cloudflare-ai-gateway": {
7+
"models": {
8+
"anthropic/claude-sonnet-4-5": {}
9+
}
10+
}
11+
},
12+
"permission": {
13+
"read": "allow",
14+
"edit": "allow",
15+
"glob": "allow",
16+
"grep": "allow",
17+
"bash": "deny",
18+
"task": "allow",
19+
"skill": "allow",
20+
"todoread": "allow",
21+
"todowrite": "allow",
22+
"webfetch": "deny"
23+
}
24+
}

.github/skills/issue-review.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
name: github-issue-review
3+
description: Triage a GitHub issue on cloudflare/workers-sdk. Assesses quality, identifies the component, checks for duplicates, and recommends next steps.
4+
---
5+
6+
# GitHub Issue Triage Skill
7+
8+
This skill triages a GitHub issue to assess its quality, identify the affected component, and recommend next steps for the maintainers.
9+
10+
**Important:** This skill runs without bash or network access. All analysis is based on the pre-fetched issue data in `context.json`. Use only `read`, `glob`, `grep`, `edit`, and `write` tools.
11+
12+
## Input
13+
14+
Pre-fetched issue data at `data/<issue_number>/context.json` containing the full issue details from the GitHub API (title, body, comments, labels, author, dates).
15+
16+
## Triage Process
17+
18+
Perform the following steps in order. **Stop and skip to Output as soon as you have sufficient evidence for a recommendation.**
19+
20+
### Step 1: Load Issue Details
21+
22+
Read the pre-fetched `context.json` file using the `read` tool.
23+
24+
Extract and note:
25+
26+
- Title and description
27+
- Issue type (bug report vs feature request vs question)
28+
- Product/component affected
29+
- Version reported against (if any)
30+
- Operating system (if any)
31+
- Reproduction steps or reproduction link (if any)
32+
- Error messages (if any)
33+
- Comment count and any relevant comment content
34+
35+
### Step 2: Check for Empty or Invalid Issues
36+
37+
Recommend **CLOSE** if:
38+
39+
- The body is empty or contains only template headers with no content filled in
40+
- The issue is clearly spam or off-topic (not related to workers-sdk)
41+
- The reporter confirmed the issue is resolved (in comments)
42+
- A maintainer indicated it should be closed (in comments)
43+
44+
Recommend **NEEDS MORE INFO** if:
45+
46+
- Bug report has no reproduction steps or link (the template requires one)
47+
- Bug report is missing version information
48+
- The description is too vague to act on
49+
50+
**STOP HERE if** the issue is clearly closeable or clearly needs more info. Skip to Output.
51+
52+
### Step 3: Identify Component
53+
54+
Map the issue to a package based on labels, title, and body content:
55+
56+
| Signal | Package |
57+
| -------------------------------------------------------- | ----------------------------------------------- |
58+
| `wrangler` label, wrangler CLI commands, `wrangler.toml` | `packages/wrangler` |
59+
| `miniflare` label, local dev simulation | `packages/miniflare` |
60+
| `d1` label, D1 database | `packages/wrangler` (D1 code is in wrangler) |
61+
| `vitest` label, worker tests | `packages/vitest-pool-workers` |
62+
| `vite-plugin` label, vite dev | `packages/vite-plugin-cloudflare` |
63+
| `c3` label, `create-cloudflare`, project scaffolding | `packages/create-cloudflare` |
64+
| `pages` label, Pages deployment | `packages/wrangler` (Pages code is in wrangler) |
65+
| R2, KV, Queues, Durable Objects bindings | `packages/wrangler` |
66+
| Workers runtime behavior (not tooling) | May be a platform issue, not workers-sdk |
67+
68+
If the issue describes Workers **runtime** behavior (e.g., fetch API quirks, V8 issues, compatibility flags) rather than **tooling** behavior, note that it may belong in a different repo.
69+
70+
### Step 4: Assess Reproducibility and Severity
71+
72+
For **bug reports**, evaluate:
73+
74+
- **Has reproduction?** Does the issue include a minimal repro link or clear steps?
75+
- **Severity estimate:** Is this a crash, data loss, incorrect behavior, or cosmetic issue?
76+
- **Scope:** Does it affect all users or a specific configuration?
77+
- **Workaround available?** Did the reporter or comments mention one?
78+
79+
For **feature requests**, evaluate:
80+
81+
- **Clarity:** Is the proposed solution clearly described?
82+
- **Use case:** Is the motivation explained?
83+
- **Scope:** Small enhancement vs large new feature?
84+
85+
## Output Format
86+
87+
### Report Directory Structure
88+
89+
```
90+
./data/<issue_number>/
91+
├── report.md # Full detailed report
92+
└── summary.md # Single-line tab-separated summary
93+
```
94+
95+
### Output Step 1: Write Full Report
96+
97+
Write the full report to `./data/<issue_number>/report.md`:
98+
99+
```markdown
100+
# Issue Triage: <owner/repo>#<number>
101+
102+
## Summary
103+
104+
<One-line summary of the issue>
105+
106+
## Classification
107+
108+
- **Type:** <Bug | Feature Request | Question | Discussion>
109+
- **Component:** <package name or "unknown">
110+
- **Severity:** <Critical | High | Medium | Low | N/A>
111+
- **Has Reproduction:** <Yes (with link) | No | N/A>
112+
- **Quality:** <Complete | Partial | Incomplete>
113+
114+
## Findings
115+
116+
- **Created:** <date>
117+
- **Author:** <username>
118+
- **Version:** <reported version, or "not specified">
119+
- **Labels:** <labels, or "none">
120+
- **Comments:** <count>
121+
122+
### Analysis
123+
124+
<2-5 bullet points covering what you found — component identification,
125+
reproducibility assessment, severity reasoning. Only include what's relevant.>
126+
127+
## Recommendation
128+
129+
**Status:** <CLOSE | KEEP OPEN | NEEDS MORE INFO | NEEDS VERIFICATION>
130+
131+
**Reasoning:** <2-3 sentences explaining why>
132+
133+
**Action:** <What a maintainer should do next>
134+
135+
**Suggested Labels:** <labels to add, if any>
136+
137+
### Suggested Comment
138+
139+
> <The exact comment to post on the issue, if applicable. For NEEDS MORE INFO,
140+
> ask specific questions. For CLOSE, explain why.
141+
> Omit this section if no comment is needed.>
142+
```
143+
144+
### Output Step 2: Write Summary File
145+
146+
Write a single tab-separated line to `./data/<issue_number>/summary.md` with these 7 fields:
147+
148+
```
149+
[<issue_number>](https://github.com/<owner>/<repo>/issues/<issue_number>) <title> <CLOSE|KEEP OPEN|NEEDS MORE INFO|NEEDS VERIFICATION> <easy|medium|hard|n/a> <brief reasoning> <brief suggested action> <Yes|No|N/A>
150+
```
151+
152+
**Column definitions:**
153+
154+
- **Issue #**: Link to the issue in markdown format `[number](url)`
155+
- **Title**: Issue title (remove any emoji prefixes like "Bug:")
156+
- **Recommendation**: One of CLOSE, KEEP OPEN, NEEDS MORE INFO, NEEDS VERIFICATION
157+
- **Difficulty**: Estimated fix difficulty - `easy`, `medium`, `hard`, or `n/a` (for feature requests or closures)
158+
- **Reasoning**: Brief summary of why (1-2 sentences)
159+
- **Suggested Action**: Brief description of next steps
160+
- **Suggested Comment**: "Yes" if a comment template is provided, "No" or "N/A" otherwise
161+
162+
**CRITICAL:** Use actual tab characters (`\t`) as column delimiters. Write only the single data line, no header.

.github/workflows/triage-issue.yml

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
name: Triage New Issue
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
workflow_dispatch:
7+
inputs:
8+
issue-number:
9+
description: "Issue number to triage"
10+
required: true
11+
type: number
12+
13+
permissions:
14+
contents: read
15+
issues: read
16+
17+
# Cancel in-progress runs for the same issue
18+
concurrency:
19+
group: triage-${{ github.event.issue.number || inputs.issue-number }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
triage:
24+
runs-on: ubuntu-latest
25+
# For issue events: skip PRs and bot-created issues. Always run for workflow_dispatch.
26+
if: >-
27+
github.event_name == 'workflow_dispatch' ||
28+
(!github.event.issue.pull_request &&
29+
github.event.issue.user.type != 'Bot')
30+
timeout-minutes: 15
31+
steps:
32+
- name: Resolve issue number
33+
id: issue
34+
run: |
35+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
36+
echo "number=${{ inputs.issue-number }}" >> "$GITHUB_OUTPUT"
37+
else
38+
echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT"
39+
fi
40+
41+
- name: Checkout (sparse — just the skill and opencode config)
42+
uses: actions/checkout@v4
43+
with:
44+
sparse-checkout: |
45+
.github/skills
46+
.github/opencode.json
47+
48+
- name: Install OpenCode
49+
run: |
50+
npm install -g opencode-ai
51+
cp .github/opencode.json ./opencode.json
52+
53+
- name: Fetch issue data
54+
env:
55+
GH_TOKEN: ${{ github.token }}
56+
run: |
57+
ISSUE=${{ steps.issue.outputs.number }}
58+
mkdir -p data/${ISSUE}
59+
60+
gh issue view ${ISSUE} \
61+
--repo ${{ github.repository }} \
62+
--json number,title,body,comments,createdAt,updatedAt,labels,state,author \
63+
> data/${ISSUE}/context.json
64+
65+
- name: Analyze issue with OpenCode
66+
env:
67+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
68+
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
69+
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
70+
run: |
71+
ISSUE=${{ steps.issue.outputs.number }}
72+
REPO=${{ github.repository }}
73+
74+
opencode run \
75+
--print-logs \
76+
"Analyze GitHub issue ${REPO}#${ISSUE} using the skill at .github/skills/issue-review.md.
77+
78+
The issue data has been pre-fetched to data/${ISSUE}/context.json.
79+
Read it with the read tool and follow the triage process.
80+
81+
Create:
82+
- data/${ISSUE}/report.md (full report)
83+
- data/${ISSUE}/summary.md (single tab-separated summary line)
84+
"
85+
86+
- name: Upload report to dashboard
87+
env:
88+
CF_ACCESS_CLIENT_ID: ${{ secrets.CF1_ACCESS_CLIENT_ID }}
89+
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF1_ACCESS_CLIENT_SECRET }}
90+
DASHBOARD_URL: ${{ secrets.REPORTS_DASHBOARD_URL }}
91+
run: |
92+
ISSUE=${{ steps.issue.outputs.number }}
93+
94+
# Check files were created
95+
if [ ! -f "data/${ISSUE}/report.md" ]; then
96+
echo "::error::report.md was not generated"
97+
exit 1
98+
fi
99+
if [ ! -f "data/${ISSUE}/summary.md" ]; then
100+
echo "::error::summary.md was not generated"
101+
exit 1
102+
fi
103+
104+
# Parse the tab-separated summary.md into variables
105+
SUMMARY=$(cat "data/${ISSUE}/summary.md")
106+
TITLE=$(echo "$SUMMARY" | cut -f2)
107+
RECOMMENDATION=$(echo "$SUMMARY" | cut -f3)
108+
DIFFICULTY=$(echo "$SUMMARY" | cut -f4)
109+
REASONING=$(echo "$SUMMARY" | cut -f5)
110+
SUGGESTED_ACTION=$(echo "$SUMMARY" | cut -f6)
111+
SUGGESTED_COMMENT=$(echo "$SUMMARY" | cut -f7)
112+
REPORT=$(cat "data/${ISSUE}/report.md")
113+
114+
# Build JSON payload
115+
PAYLOAD=$(jq -n \
116+
--arg title "$TITLE" \
117+
--arg githubUrl "https://github.com/${{ github.repository }}/issues/${ISSUE}" \
118+
--arg recommendation "$RECOMMENDATION" \
119+
--arg difficulty "$DIFFICULTY" \
120+
--arg reasoning "$REASONING" \
121+
--arg suggestedAction "$SUGGESTED_ACTION" \
122+
--arg suggestedComment "$SUGGESTED_COMMENT" \
123+
--arg reportMarkdown "$REPORT" \
124+
'{
125+
title: $title,
126+
githubUrl: $githubUrl,
127+
recommendation: $recommendation,
128+
difficulty: $difficulty,
129+
reasoning: $reasoning,
130+
suggestedAction: $suggestedAction,
131+
suggestedComment: $suggestedComment,
132+
reportMarkdown: $reportMarkdown
133+
}')
134+
135+
# POST to dashboard API
136+
HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" \
137+
-X POST "${DASHBOARD_URL}/api/report/${ISSUE}" \
138+
-H "Content-Type: application/json" \
139+
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
140+
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
141+
-d "$PAYLOAD")
142+
143+
echo "Dashboard API response: HTTP ${HTTP_STATUS}"
144+
cat response.json
145+
146+
if [ "$HTTP_STATUS" != "200" ]; then
147+
echo "::error::Failed to upload report (HTTP ${HTTP_STATUS})"
148+
exit 1
149+
fi
150+
151+
echo "Report uploaded for issue #${ISSUE}"

0 commit comments

Comments
 (0)