Skip to content

Claude Auto-Triage #1121

Claude Auto-Triage

Claude Auto-Triage #1121

name: Claude Auto-Triage
on:
workflow_run:
workflows: ["Claude Code Review"]
types: [completed]
jobs:
triage:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
concurrency:
group: claude-automation
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
issues: write
actions: read
statuses: write
id-token: write
steps:
- name: Get PR from workflow run
id: pr
env:
GH_TOKEN: ${{ github.token }}
run: |
HEAD_BRANCH="${{ github.event.workflow_run.head_branch }}"
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
PR_NUMBER=$(gh pr list \
--repo "${{ github.repository }}" \
--state open \
--head "$HEAD_BRANCH" \
--json number \
--jq '.[0].number')
if [ -z "$PR_NUMBER" ]; then
echo "No open PR found for branch $HEAD_BRANCH"
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "Found PR #$PR_NUMBER"
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "skip=false" >> $GITHUB_OUTPUT
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
# Get PR details
PR_DATA=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json headRefName,title,headRepositoryOwner)
echo "head_ref=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> $GITHUB_OUTPUT
# Check if fork
HEAD_OWNER=$(echo "$PR_DATA" | jq -r '.headRepositoryOwner.login')
if [ "$HEAD_OWNER" != "${{ github.repository_owner }}" ]; then
echo "is_fork=true" >> $GITHUB_OUTPUT
else
echo "is_fork=false" >> $GITHUB_OUTPUT
fi
- name: Check skip labels
if: steps.pr.outputs.skip != 'true'
id: labels
env:
GH_TOKEN: ${{ github.token }}
run: |
LABELS=$(gh pr view ${{ steps.pr.outputs.number }} --repo "${{ github.repository }}" --json labels --jq '.labels[].name')
if echo "$LABELS" | grep -q "do-not-auto-merge\|needs-human-review"; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "::notice::PR has skip label, skipping triage"
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Report pending status
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \
-f state=pending \
-f context="triage" \
-f description="Triage in progress..."
- name: Checkout repository
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.pr.outputs.head_ref }}
- name: Setup Claude Code
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
id: setup-claude
uses: ./.github/actions/setup-claude-code
- name: Run Claude Auto-Triage
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
id: triage
uses: anthropics/claude-code-action@v1
env:
GH_TOKEN: ${{ secrets.PAT_WORKFLOW_TRIGGER || github.token }}
with:
allowed_bots: "claude"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
path_to_claude_code_executable: ${{ steps.setup-claude.outputs.executable-path }}
prompt: |
# Auto-Triage PR #${{ steps.pr.outputs.number }}
**Title**: ${{ steps.pr.outputs.title }}
**Is Fork**: ${{ steps.pr.outputs.is_fork }}
## Task
1. Read review comments: `gh pr view ${{ steps.pr.outputs.number }} --comments`
2. For each issue found, decide: **FIX_NOW**, **DEFER_ISSUE**, or **IGNORE**
3. Execute actions and post summary
## Decision Rules
**FIX_NOW** (max 3 items, batch in one comment):
- Mechanical fixes (rename, remove debug code, add null checks)
- In-scope incomplete work
- If fork PR: convert to DEFER_ISSUE instead
**DEFER_ISSUE** (create GitHub issue):
- Complex changes, architectural suggestions
- Out-of-scope improvements
- Use `quick-fix` label for trivial out-of-scope fixes
- Use `from-pr-review` label for all deferred issues
**IGNORE**: False positives, existing issues cover it, pure style preference
## Execute Actions
**For FIX_NOW** (batch all in ONE comment):
```bash
gh pr comment ${{ steps.pr.outputs.number }} --body "@claude please fix:
1. [issue]: [fix instructions]
2. [issue]: [fix instructions]
..."
```
**For DEFER_ISSUE**:
```bash
gh issue create --title "[From PR #${{ steps.pr.outputs.number }}] [desc]" \
--label "from-pr-review" --body "..."
```
**Post summary** (no @claude prefix):
```bash
gh pr comment ${{ steps.pr.outputs.number }} --body "## Auto-Triage Summary
| Issue | Decision | Action |
|-------|----------|--------|
| ... | FIX_NOW | In fix request |
| ... | DEFER_ISSUE | Created #N |"
```
**If no FIX_NOW items**, add ready-to-merge label:
```bash
gh pr edit ${{ steps.pr.outputs.number }} --add-label "ready-to-merge"
```
claude_args: '--model claude-sonnet-4-6 --max-turns 30 --allowed-tools "Read,Glob,Grep,Bash,Task"'
show_full_output: true
- name: Check for pending fixes
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
id: check-fixes
env:
GH_TOKEN: ${{ github.token }}
run: |
# Get all comments
COMMENTS=$(gh pr view ${{ steps.pr.outputs.number }} \
--repo "${{ github.repository }}" \
--json comments)
# Count fix requests (@claude.*fix)
FIX_REQUESTS=$(echo "$COMMENTS" | jq '[.comments[] | select(.body | test("@claude.*fix"; "i"))] | length')
# Count fix completions (## Fix Summary)
FIX_COMPLETIONS=$(echo "$COMMENTS" | jq '[.comments[] | select(.body | test("## Fix Summary"))] | length')
echo "fix_requests=$FIX_REQUESTS" >> $GITHUB_OUTPUT
echo "fix_completions=$FIX_COMPLETIONS" >> $GITHUB_OUTPUT
# Pending fixes = requests that don't have corresponding completions
if [ "$FIX_REQUESTS" -gt "$FIX_COMPLETIONS" ]; then
echo "has_pending_fixes=true" >> $GITHUB_OUTPUT
echo "Pending fixes: $FIX_REQUESTS requests, $FIX_COMPLETIONS completions"
else
echo "has_pending_fixes=false" >> $GITHUB_OUTPUT
echo "All fixes resolved: $FIX_REQUESTS requests, $FIX_COMPLETIONS completions"
fi
- name: Enable auto-merge
if: steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true' && steps.check-fixes.outputs.has_pending_fixes != 'true'
env:
GH_TOKEN: ${{ secrets.PAT_WORKFLOW_TRIGGER }}
run: |
gh pr merge ${{ steps.pr.outputs.number }} \
--repo "${{ github.repository }}" \
--auto --squash --delete-branch || true
- name: Report status
if: always() && steps.pr.outputs.skip != 'true' && steps.labels.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ steps.check-fixes.outputs.has_pending_fixes }}" = "true" ]; then
STATE="failure"
DESC="Fixes pending"
else
STATE="success"
DESC="Triage complete"
fi
gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \
-f state="$STATE" -f context="triage" -f description="$DESC"