Files
wehub-resource-sync 70bf21e064
Handle Changesets / Handle Changesets (push) Waiting to run
Semgrep OSS scan / semgrep-oss (push) Waiting to run
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

236 lines
8.4 KiB
YAML

name: Triage Issue
on:
issues:
types: [opened]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue-number:
description: "Issue number to triage"
required: true
type: number
permissions:
contents: read
# Writes (comment + labels) are performed with GH_ACCESS_TOKEN (the
# workers-devprod bot) so they are attributed to that identity, not
# github-actions[bot]. Only read access is needed from the default token.
issues: read
# Cancel in-progress runs for the same issue
concurrency:
group: triage-${{ github.event.issue.number || inputs.issue-number }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
jobs:
triage:
runs-on: ubuntu-latest
# Always run for manual dispatch. For issue events, skip PRs and
# bot-created issues. For comment events, skip PRs, bot comments, and the
# triage bot's own sticky comment (identified by the marocchino marker) to
# avoid infinite re-triage loops.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issues' &&
!github.event.issue.pull_request &&
github.event.issue.user.type != 'Bot') ||
(github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.comment.body, '<!-- Sticky Pull Request Comment'))
timeout-minutes: 60
steps:
- name: Resolve issue number
id: issue
env:
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
EVENT_NAME: ${{ github.event_name }}
INPUT_ISSUE_NUMBER: ${{ inputs.issue-number }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "number=${INPUT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
else
echo "number=${EVENT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout (sparse — just the skill and opencode config)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
sparse-checkout: |
.github/skills
.github/opencode.json
- name: Install OpenCode
run: |
npm install -g opencode-ai
cp .github/opencode.json ./opencode.json
- name: Fetch issue data
env:
GH_TOKEN: ${{ github.token }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
mkdir -p "data/${ISSUE}"
gh issue view "$ISSUE" \
--repo "$REPO" \
--json number,title,body,comments,createdAt,updatedAt,labels,state,author \
> "data/${ISSUE}/context.json"
- name: Analyze issue with OpenCode
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
opencode run \
--print-logs \
"Analyze GitHub issue ${REPO}#${ISSUE} using the skill at .github/skills/issue-review.md.
The issue data has been pre-fetched to data/${ISSUE}/context.json.
Read it with the read tool and follow the triage process.
Create:
- data/${ISSUE}/report.md (full markdown report)
- data/${ISSUE}/summary.json (structured JSON summary)
"
- name: Upload report to dashboard
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF1_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF1_ACCESS_CLIENT_SECRET }}
DASHBOARD_URL: ${{ secrets.REPORTS_DASHBOARD_URL }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
# Check files were created
if [ ! -f "data/${ISSUE}/report.md" ]; then
echo "::error::report.md was not generated"
exit 1
fi
if [ ! -f "data/${ISSUE}/summary.json" ]; then
echo "::error::summary.json was not generated"
exit 1
fi
# Validate the summary is parseable JSON before doing anything with it.
if ! jq empty "data/${ISSUE}/summary.json" 2>/dev/null; then
echo "::error::summary.json is not valid JSON"
exit 1
fi
# Build the dashboard payload from the structured summary + markdown report.
# suggestedComment is kept as a "Yes"/"No" string for dashboard compatibility.
PAYLOAD=$(jq \
--arg githubUrl "https://github.com/${REPO}/issues/${ISSUE}" \
--rawfile reportMarkdown "data/${ISSUE}/report.md" \
'{
title: .title,
githubUrl: $githubUrl,
recommendation: .recommendation,
difficulty: .difficulty,
reasoning: .reasoning,
suggestedAction: .suggestedAction,
suggestedComment: (if .hasSuggestedComment then "Yes" else "No" end),
reportMarkdown: $reportMarkdown
}' "data/${ISSUE}/summary.json")
# POST to dashboard API
HTTP_STATUS=$(curl -L --post302 -s -o response.json -w "%{http_code}" \
-X POST "${DASHBOARD_URL}/api/report/${ISSUE}" \
-H "Content-Type: application/json" \
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
-d "$PAYLOAD")
echo "Dashboard API response: HTTP ${HTTP_STATUS}"
cat response.json
if [ "$HTTP_STATUS" != "200" ]; then
echo "::error::Failed to upload report (HTTP ${HTTP_STATUS})"
exit 1
fi
echo "Report uploaded for issue #${ISSUE}"
- name: Build triage comment
env:
ISSUE: ${{ steps.issue.outputs.number }}
run: |
{
echo "> [!NOTE]"
echo "> This is an automated, advisory triage report generated by workers-devprod. It is not an official maintainer response — a maintainer will follow up."
echo ""
echo "<details>"
echo "<summary>🤖 Automated triage report</summary>"
echo ""
cat "data/${ISSUE}/report.md"
echo ""
echo "</details>"
echo ""
echo "<details>"
echo "<summary>Structured summary (JSON)</summary>"
echo ""
echo '```json'
cat "data/${ISSUE}/summary.json"
echo ""
echo '```'
echo ""
echo "</details>"
} > "data/${ISSUE}/comment.md"
- name: Post triage comment
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: triage-report
number: ${{ steps.issue.outputs.number }}
path: data/${{ steps.issue.outputs.number }}/comment.md
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Apply suggested labels
env:
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
SUMMARY_PATH="data/${ISSUE}/summary.json"
if [ ! -f "$SUMMARY_PATH" ]; then
echo "No summary.json; skipping label application"
exit 0
fi
# suggestedLabels is a JSON array; emit one label per line.
jq -r '.suggestedLabels // [] | .[]' "$SUMMARY_PATH" > suggested-labels.txt
if [ ! -s suggested-labels.txt ]; then
echo "No suggested labels"
exit 0
fi
# Only apply labels that actually exist in the repo.
gh label list --repo "$REPO" --limit 500 --json name --jq '.[].name' > repo-labels.txt
ARGS=()
while IFS= read -r label; do
if [ -z "$label" ]; then
continue
fi
if grep -Fxq "$label" repo-labels.txt; then
ARGS+=(--add-label "$label")
else
echo "Skipping unknown label: ${label}"
fi
done < suggested-labels.txt
if [ ${#ARGS[@]} -eq 0 ]; then
echo "No valid labels to apply"
exit 0
fi
gh issue edit "$ISSUE" --repo "$REPO" "${ARGS[@]}"