Skip to content

Commit 564ac1a

Browse files
committed
(feat): add Claude PR automation workflows
1 parent 20c90b4 commit 564ac1a

3 files changed

Lines changed: 381 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
concurrency:
14+
group: claude-comments-${{ github.event.issue.number || github.event.pull_request.number }}-${{ github.event.comment.id || github.event.review.id || github.run_id }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
claude:
19+
if: |
20+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
21+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
22+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
23+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
24+
runs-on: self-hosted
25+
permissions:
26+
contents: write
27+
pull-requests: write
28+
issues: write
29+
id-token: write
30+
actions: read
31+
steps:
32+
- name: Checkout repository
33+
uses: actions/checkout@v6
34+
with:
35+
fetch-depth: 20
36+
37+
- name: Classify task complexity with Haiku
38+
id: classify
39+
uses: ./.github/actions/classify-complexity
40+
with:
41+
oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
42+
prompt: |
43+
Classify this coding task's complexity. Reply with EXACTLY one lowercase word and nothing else: 'simple' or 'complex'.
44+
45+
simple = typo fix, docs tweak, one-file obvious bug, rename, trivial refactor within a single function
46+
complex = multi-file change, new feature, architecture or API change, deep debugging, performance work, anything with unclear scope or touching more than ~3 files
47+
48+
TITLE: ${{ github.event.issue.title }}
49+
50+
REQUEST:
51+
${{ github.event.comment.body || github.event.review.body || github.event.issue.body }}
52+
53+
- name: Run Claude Code
54+
id: claude
55+
uses: anthropics/claude-code-action@v1
56+
with:
57+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
58+
plugin_marketplaces: |
59+
https://github.com/anthropics/claude-code.git
60+
https://github.com/abnegate/claudes.git
61+
plugins: 'skills@claudes'
62+
additional_permissions: |
63+
actions: read
64+
use_sticky_comment: true
65+
use_commit_signing: true
66+
exclude_comments_by_actor: 'dependabot[bot],renovate[bot]'
67+
claude_args: |
68+
--model ${{ steps.classify.outputs.model }}
69+
--fallback-model ${{ steps.classify.outputs.fallback }}
70+
--dangerously-skip-permissions
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: Claude CI Watcher
2+
3+
on:
4+
workflow_run:
5+
workflows: [CI]
6+
types: [completed]
7+
8+
# Shared with claude-review: both mutate the PR branch (commit + push), so they
9+
# must not run simultaneously. Keying on head branch serializes reviewer and
10+
# watcher runs targeting the same PR into a single queue.
11+
concurrency:
12+
group: claude-pr-${{ github.event.workflow_run.head_branch }}
13+
cancel-in-progress: false
14+
15+
jobs:
16+
fix-failures:
17+
# Only fire on PR failures (not push-to-main) and skip fork PRs.
18+
if: >
19+
github.event.workflow_run.conclusion == 'failure' &&
20+
github.event.workflow_run.event == 'pull_request' &&
21+
github.event.workflow_run.head_repository.full_name == github.repository
22+
runs-on: self-hosted
23+
permissions:
24+
contents: write
25+
pull-requests: write
26+
issues: read
27+
actions: read
28+
checks: read
29+
id-token: write
30+
31+
steps:
32+
- name: Find the PR
33+
id: pr
34+
env:
35+
GH_TOKEN: ${{ github.token }}
36+
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
37+
run: |
38+
pr_number=$(gh pr list --repo "$GITHUB_REPOSITORY" \
39+
--head "$HEAD_BRANCH" --state open --json number --jq '.[0].number')
40+
if [ -z "$pr_number" ]; then
41+
echo "No open PR found — skipping."
42+
echo "skip=true" >> "$GITHUB_OUTPUT"
43+
else
44+
echo "number=$pr_number" >> "$GITHUB_OUTPUT"
45+
echo "skip=false" >> "$GITHUB_OUTPUT"
46+
fi
47+
48+
- name: Checkout PR branch
49+
if: steps.pr.outputs.skip != 'true'
50+
uses: actions/checkout@v6
51+
with:
52+
ref: ${{ github.event.workflow_run.head_branch }}
53+
fetch-depth: 10
54+
55+
- name: Get failure logs
56+
if: steps.pr.outputs.skip != 'true'
57+
env:
58+
GH_TOKEN: ${{ github.token }}
59+
RUN_ID: ${{ github.event.workflow_run.id }}
60+
run: |
61+
gh run view "$RUN_ID" --log-failed 2>&1 | tail -200 > /tmp/ci-failure-logs.txt
62+
echo "Captured $(wc -l < /tmp/ci-failure-logs.txt) lines of failure logs"
63+
64+
- name: Extract failure log excerpt
65+
if: steps.pr.outputs.skip != 'true'
66+
id: excerpt
67+
run: |
68+
{
69+
echo 'log<<__EOF__'
70+
tail -120 /tmp/ci-failure-logs.txt
71+
echo '__EOF__'
72+
} >> "$GITHUB_OUTPUT"
73+
74+
- name: Classify failure complexity with Haiku
75+
if: steps.pr.outputs.skip != 'true'
76+
id: classify
77+
uses: ./.github/actions/classify-complexity
78+
with:
79+
oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
80+
prompt: |
81+
Classify this CI failure's fix complexity. Reply with EXACTLY one lowercase word and nothing else: 'simple' or 'complex'.
82+
83+
simple = lint violation, formatting, obvious typo, missing import, trivial test assertion fix, single-file obvious mistake
84+
complex = compile error with unclear cause, real test failure exposing a bug, flaky infrastructure, multi-file breakage, anything with unclear root cause or scope
85+
86+
FAILURE LOGS (tail):
87+
${{ steps.excerpt.outputs.log }}
88+
89+
- name: Claude fixes CI failures
90+
if: steps.pr.outputs.skip != 'true'
91+
uses: anthropics/claude-code-action@v1
92+
env:
93+
PR_NUMBER: ${{ steps.pr.outputs.number }}
94+
FAILED_RUN_ID: ${{ github.event.workflow_run.id }}
95+
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
96+
with:
97+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
98+
plugin_marketplaces: |
99+
https://github.com/anthropics/claude-code.git
100+
https://github.com/abnegate/claudes.git
101+
plugins: 'skills@claudes'
102+
use_sticky_comment: 'false'
103+
use_commit_signing: 'true'
104+
show_full_output: 'true'
105+
claude_args: |
106+
--model ${{ steps.classify.outputs.model }}
107+
--fallback-model ${{ steps.classify.outputs.fallback }}
108+
--dangerously-skip-permissions
109+
prompt: |
110+
CI failed on PR #$PR_NUMBER (run $FAILED_RUN_ID).
111+
112+
The failure logs are at /tmp/ci-failure-logs.txt. Read them first.
113+
114+
## Coordination with the code reviewer
115+
The `claude-review` workflow also pushes to this PR branch. We share a
116+
concurrency group (branch-keyed), so only one of us runs at a time — but
117+
the reviewer may have pushed fix commits between the failing CI run and
118+
this job starting. Before STEP 6, `git fetch origin` and
119+
`git pull --rebase origin $HEAD_BRANCH`. If rebase conflicts, resolve them
120+
(prefer the reviewer's changes unless they directly contradict your CI fix),
121+
then continue. After rebasing, re-check whether the CI failure is still
122+
reproducible — the reviewer may have already fixed it, in which case post
123+
a comment saying so and STOP without pushing.
124+
125+
Your job:
126+
1. Read the failure logs to identify the root cause (compile error, test failure, lint violation, etc.)
127+
2. Read the project's CLAUDE.md for build/test/lint commands and conventions
128+
3. Fix the issue directly in the codebase
129+
4. Verify your fix by re-running the failed step (use whatever build system the project uses)
130+
5. Post a brief PR comment explaining what failed and what you fixed:
131+
`gh pr comment $PR_NUMBER --repo $GITHUB_REPOSITORY --body "..."`
132+
6. `git fetch origin && git pull --rebase origin $HEAD_BRANCH`, then commit with
133+
message: `(fix): CI — <short description>`
134+
7. Push to the PR branch
135+
136+
Rules:
137+
- Only fix the CI failure. Don't refactor, clean up, or improve other code.
138+
- If the failure is a flaky test (passes on retry), just re-run it and comment that it was flaky.
139+
- If you can't determine the root cause, post a comment asking for help instead of guessing.
140+
- Never push to main. Only push to the PR head branch.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
name: Claude Code Improvements
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, ready_for_review, reopened]
6+
7+
# Shared with claude-watcher: both mutate the PR branch (commit + push), so they
8+
# must not run simultaneously. Keying on head branch serializes reviewer and
9+
# watcher runs targeting the same PR into a single queue.
10+
concurrency:
11+
group: claude-pr-${{ github.event.pull_request.head.ref }}
12+
cancel-in-progress: false
13+
14+
jobs:
15+
claude-review:
16+
runs-on: self-hosted
17+
if: github.event.pull_request.head.repo.full_name == github.repository
18+
permissions:
19+
contents: write
20+
pull-requests: write
21+
issues: read
22+
id-token: write
23+
24+
steps:
25+
- name: Checkout PR branch
26+
uses: actions/checkout@v6
27+
with:
28+
ref: ${{ github.event.pull_request.head.ref }}
29+
fetch-depth: 0
30+
31+
- name: Compute PR diff stats
32+
id: diff
33+
env:
34+
BASE_REF: ${{ github.event.pull_request.base.ref }}
35+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
36+
run: |
37+
git fetch --no-tags origin "+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF"
38+
merge_base=$(git merge-base "origin/$BASE_REF" "$HEAD_SHA")
39+
changed_files=$(git diff --name-only "$merge_base..$HEAD_SHA" | wc -l | tr -d ' ')
40+
changed_lines=$(git diff --shortstat "$merge_base..$HEAD_SHA" | awk '{ ins=0; del=0; for (i=1;i<=NF;i++) { if ($i ~ /insertion/) ins=$(i-1); if ($i ~ /deletion/) del=$(i-1) } print ins + del }')
41+
changed_lines=${changed_lines:-0}
42+
43+
{
44+
echo "files=$changed_files"
45+
echo "lines=$changed_lines"
46+
echo 'file_list<<__EOF__'
47+
git diff --name-only "$merge_base..$HEAD_SHA" | head -50
48+
echo '__EOF__'
49+
} >> "$GITHUB_OUTPUT"
50+
51+
- name: Classify PR complexity with Haiku
52+
id: classify
53+
uses: ./.github/actions/classify-complexity
54+
with:
55+
oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
56+
prompt: |
57+
Classify this pull request's review complexity. Reply with EXACTLY one lowercase word and nothing else: 'simple' or 'complex'.
58+
59+
simple = typo fix, docs tweak, one-file obvious bug, rename, trivial refactor within a single function, small test-only change
60+
complex = multi-file change, new feature, architecture or API change, deep debugging, performance work, security-sensitive code, anything with unclear scope or touching more than ~3 files
61+
62+
TITLE: ${{ github.event.pull_request.title }}
63+
64+
STATS: ${{ steps.diff.outputs.files }} files, ${{ steps.diff.outputs.lines }} lines changed
65+
66+
FILES:
67+
${{ steps.diff.outputs.file_list }}
68+
69+
DESCRIPTION:
70+
${{ github.event.pull_request.body }}
71+
72+
- name: Run Claude Code Review
73+
id: claude-review
74+
uses: anthropics/claude-code-action@v1
75+
env:
76+
PR_NUMBER: ${{ github.event.pull_request.number }}
77+
REPO: ${{ github.repository }}
78+
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
79+
with:
80+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
81+
plugin_marketplaces: |
82+
https://github.com/anthropics/claude-code.git
83+
https://github.com/abnegate/claudes.git
84+
plugins: |
85+
code-review@claude-code-plugins
86+
skills@claudes
87+
use_sticky_comment: 'false'
88+
use_commit_signing: 'true'
89+
show_full_output: 'true'
90+
claude_args: |
91+
--model ${{ steps.classify.outputs.model }}
92+
--fallback-model ${{ steps.classify.outputs.fallback }}
93+
--dangerously-skip-permissions
94+
prompt: |
95+
You are reviewing and fixing PR #$PR_NUMBER in $REPO.
96+
97+
## Coordination with the CI watcher
98+
The `claude-watcher` workflow also pushes to this PR branch when CI fails.
99+
We share a concurrency group (branch-keyed), so only one of us runs at a time —
100+
but the OTHER may have pushed between the last event and this job starting.
101+
Before committing in STEP 5, `git fetch origin` and `git pull --rebase origin <branch>`.
102+
If rebase conflicts, resolve them (prefer the other side's changes unless they
103+
contradict a finding you're fixing), then continue.
104+
105+
## STEP 1 — Analyze
106+
Run `/code-review:code-review` for all CRITICAL/HIGH/MEDIUM findings. Skip low/nits.
107+
108+
## STEP 2 — Post inline review
109+
Build /tmp/review.json with this structure and post it:
110+
```json
111+
{
112+
"event": "COMMENT",
113+
"body": "## Code Review\n\n**N finding(s)**\n\nSee inline comments. Fixes incoming.",
114+
"comments": [
115+
{"path": "file.php", "line": 42, "body": "**[SEVERITY]** ...\n\nExplanation + fix."}
116+
]
117+
}
118+
```
119+
Post: `gh api repos/$REPO/pulls/$PR_NUMBER/reviews --input /tmp/review.json`
120+
121+
If zero findings: post "No critical/high/medium findings." and STOP.
122+
123+
## STEP 3 — Fix in parallel via isolated worktree subagents
124+
For MAXIMUM speed, launch one Agent per finding using worktree isolation.
125+
Findings in different files run in TRUE parallel — launch them ALL in one message.
126+
Findings in the SAME file go to the SAME agent to avoid conflicts.
127+
128+
Each agent prompt must be self-contained:
129+
- Include the finding: severity, file path, line numbers, what's wrong, how to fix
130+
- Tell it to verify the fix compiles (read CLAUDE.md for the build command)
131+
- Tell it NOT to touch other files or make unrelated changes
132+
133+
Example — 3 findings in 3 files, all launched at once:
134+
Agent({description: "Fix 1", isolation: "worktree", prompt: "Fix [HIGH] ... in file.php line 42 ..."})
135+
Agent({description: "Fix 2", isolation: "worktree", prompt: "Fix [MEDIUM] ... in other.php line 99 ..."})
136+
Agent({description: "Fix 3", isolation: "worktree", prompt: "Fix [MEDIUM] ... in third.php line 7 ..."})
137+
138+
## STEP 4 — Consolidate
139+
After all agents finish, apply their changes to the main checkout:
140+
- Each worktree agent returns the files it changed
141+
- Cherry-pick or manually apply each agent's diff to the working tree
142+
- If two agents touched the same file, merge carefully
143+
- Verify final result compiles
144+
145+
## STEP 5 — Commit and push
146+
- `git fetch origin && git pull --rebase origin $HEAD_BRANCH` to absorb any
147+
commits the watcher (or the PR author) pushed while this job was queued
148+
- Capture the pre-commit tip: `BEFORE_SHA=$(git rev-parse HEAD)`
149+
- Stage only fix files
150+
- Commit: `(fix): address review findings — X HIGH, Y MEDIUM`
151+
- Body: one bullet per finding
152+
- Push to PR branch
153+
- Capture the post-push tip: `AFTER_SHA=$(git rev-parse HEAD)`
154+
155+
## STEP 6 — Post summary comment with compare link
156+
After a successful push, add a follow-up comment linking to a compare view
157+
of everything this run added, so reviewers can see exactly what changed:
158+
159+
COMPARE_URL="https://github.com/$REPO/compare/$BEFORE_SHA...$AFTER_SHA"
160+
gh pr comment $PR_NUMBER --repo $REPO --body "Fixes pushed: $COMPARE_URL
161+
162+
<bulleted list of commits: \`sha\` — subject>"
163+
164+
If BEFORE_SHA equals AFTER_SHA (nothing was actually pushed — e.g. all
165+
fixes were no-ops after rebase), skip this step.
166+
167+
Rules:
168+
- Do NOT skip findings.
169+
- Maximize parallelism — launch as many worktree agents as there are independent file groups.
170+
- Each agent prompt must be fully self-contained (it has no context from this conversation).
171+
- Never push to main.

0 commit comments

Comments
 (0)