Skip to content

Commit e90d282

Browse files
GiggleLiuclaude
andcommitted
Add PR automation workflow for Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4cb3bdb commit e90d282

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
name: PR Automation
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
jobs:
8+
execute:
9+
# Only run on PR comments (not issue comments) from repo owner/collaborators
10+
if: |
11+
github.event.issue.pull_request &&
12+
startsWith(github.event.comment.body, '[action]') ||
13+
startsWith(github.event.comment.body, '[fix]') ||
14+
startsWith(github.event.comment.body, '[debug]')
15+
16+
# Use self-hosted runner to run Claude on your local machine
17+
# Change to 'ubuntu-latest' to run on GitHub-hosted runners instead
18+
runs-on: self-hosted
19+
20+
permissions:
21+
contents: write
22+
pull-requests: write
23+
issues: write
24+
25+
steps:
26+
- name: Get PR details
27+
id: pr
28+
uses: actions/github-script@v7
29+
with:
30+
script: |
31+
const pr = await github.rest.pulls.get({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
pull_number: context.issue.number
35+
});
36+
core.setOutput('branch', pr.data.head.ref);
37+
core.setOutput('sha', pr.data.head.sha);
38+
39+
// Determine command
40+
const body = context.payload.comment.body;
41+
let command = 'unknown';
42+
if (body.startsWith('[action]')) command = 'action';
43+
else if (body.startsWith('[fix]')) command = 'fix';
44+
else if (body.startsWith('[debug]')) command = 'debug';
45+
core.setOutput('command', command);
46+
47+
- name: Post queued comment
48+
uses: actions/github-script@v7
49+
with:
50+
script: |
51+
await github.rest.issues.createComment({
52+
owner: context.repo.owner,
53+
repo: context.repo.repo,
54+
issue_number: context.issue.number,
55+
body: '[queued] Job started in GitHub Actions...'
56+
});
57+
58+
- name: Checkout PR branch
59+
uses: actions/checkout@v4
60+
with:
61+
ref: ${{ steps.pr.outputs.branch }}
62+
fetch-depth: 0
63+
64+
- name: Setup Node.js (GitHub-hosted only)
65+
if: ${{ !contains(runner.name, 'self-hosted') }}
66+
uses: actions/setup-node@v4
67+
with:
68+
node-version: '20'
69+
70+
- name: Install Claude Code CLI (GitHub-hosted only)
71+
if: ${{ !contains(runner.name, 'self-hosted') }}
72+
run: npm install -g @anthropic-ai/claude-code
73+
74+
- name: Find plan file
75+
id: plan
76+
if: steps.pr.outputs.command == 'action'
77+
run: |
78+
for f in PLAN.md plan.md .claude/plan.md docs/plan.md; do
79+
if [ -f "$f" ]; then
80+
echo "file=$f" >> $GITHUB_OUTPUT
81+
exit 0
82+
fi
83+
done
84+
# Check docs/plans/*.md
85+
latest=$(ls -t docs/plans/*.md 2>/dev/null | head -1)
86+
if [ -n "$latest" ]; then
87+
echo "file=$latest" >> $GITHUB_OUTPUT
88+
exit 0
89+
fi
90+
echo "file=" >> $GITHUB_OUTPUT
91+
92+
- name: Execute plan
93+
if: steps.pr.outputs.command == 'action' && steps.plan.outputs.file != ''
94+
id: execute
95+
env:
96+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
97+
run: |
98+
set +e
99+
OUTPUT=$(claude --dangerously-skip-permissions --max-turns 100 -p "
100+
Execute the plan in '${{ steps.plan.outputs.file }}'.
101+
102+
Instructions:
103+
1. Read the plan file carefully
104+
2. Implement each step in order
105+
3. Commit changes with descriptive messages
106+
4. Push changes: git push origin ${{ steps.pr.outputs.branch }}
107+
108+
Do NOT post comments to the PR.
109+
" 2>&1)
110+
EXIT_CODE=$?
111+
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
112+
113+
# Save output to file (for artifact)
114+
echo "$OUTPUT" > claude-output.txt
115+
116+
# Save truncated output for comment
117+
echo "output<<EOF" >> $GITHUB_OUTPUT
118+
echo "$OUTPUT" | tail -100 >> $GITHUB_OUTPUT
119+
echo "EOF" >> $GITHUB_OUTPUT
120+
121+
- name: Handle missing plan
122+
if: steps.pr.outputs.command == 'action' && steps.plan.outputs.file == ''
123+
uses: actions/github-script@v7
124+
with:
125+
script: |
126+
await github.rest.issues.createComment({
127+
owner: context.repo.owner,
128+
repo: context.repo.repo,
129+
issue_number: context.issue.number,
130+
body: '[waiting] No plan file found. Please create one of: PLAN.md, plan.md, .claude/plan.md, docs/plan.md, or docs/plans/*.md'
131+
});
132+
core.setFailed('No plan file found');
133+
134+
- name: Fix review comments
135+
if: steps.pr.outputs.command == 'fix'
136+
id: fix
137+
env:
138+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
139+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
140+
run: |
141+
set +e
142+
143+
# Fetch review comments
144+
INLINE=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/comments --jq '.[].body' 2>/dev/null || echo "")
145+
REVIEWS=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/reviews --jq '.[] | select(.body != "") | .body' 2>/dev/null || echo "")
146+
147+
OUTPUT=$(claude --dangerously-skip-permissions --max-turns 100 -p "
148+
Address the PR review comments.
149+
150+
=== Inline Review Comments ===
151+
$INLINE
152+
153+
=== PR Reviews ===
154+
$REVIEWS
155+
156+
Instructions:
157+
1. Read each comment and understand what change is requested
158+
2. Make the requested changes to the code
159+
3. Commit: git commit -am 'Address PR review feedback'
160+
4. Push: git push origin ${{ steps.pr.outputs.branch }}
161+
162+
Do NOT post comments to the PR.
163+
" 2>&1)
164+
EXIT_CODE=$?
165+
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
166+
echo "$OUTPUT" > claude-output.txt
167+
echo "output<<EOF" >> $GITHUB_OUTPUT
168+
echo "$OUTPUT" | tail -100 >> $GITHUB_OUTPUT
169+
echo "EOF" >> $GITHUB_OUTPUT
170+
171+
- name: Debug test
172+
if: steps.pr.outputs.command == 'debug'
173+
id: debug
174+
env:
175+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
176+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
177+
run: |
178+
set +e
179+
OUTPUT=$(claude --dangerously-skip-permissions --max-turns 5 -p "
180+
This is a debug test. Post a comment to PR #${{ github.event.issue.number }} on repository ${{ github.repository }} with:
181+
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body '[pass] GitHub Actions pipeline test successful'
182+
" 2>&1)
183+
EXIT_CODE=$?
184+
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
185+
echo "$OUTPUT" > claude-output.txt
186+
187+
- name: Upload Claude output
188+
if: always()
189+
uses: actions/upload-artifact@v4
190+
with:
191+
name: claude-output
192+
path: claude-output.txt
193+
retention-days: 7
194+
195+
- name: Post success comment
196+
if: success() && (steps.execute.outputs.exit_code == '0' || steps.fix.outputs.exit_code == '0')
197+
uses: actions/github-script@v7
198+
with:
199+
script: |
200+
const command = '${{ steps.pr.outputs.command }}';
201+
const msg = command === 'action' ? '[done] Plan execution completed.' : '[fixed] Review comments addressed.';
202+
const output = `${{ steps.execute.outputs.output || steps.fix.outputs.output }}`;
203+
204+
let body = msg;
205+
if (output) {
206+
body += `\n\n<details><summary>Claude output (last 100 lines)</summary>\n\n\`\`\`\n${output}\n\`\`\`\n</details>`;
207+
}
208+
body += `\n\n[View full logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
209+
210+
await github.rest.issues.createComment({
211+
owner: context.repo.owner,
212+
repo: context.repo.repo,
213+
issue_number: context.issue.number,
214+
body: body
215+
});
216+
217+
- name: Post failure comment
218+
if: failure() || steps.execute.outputs.exit_code != '0' && steps.execute.outputs.exit_code != '' || steps.fix.outputs.exit_code != '0' && steps.fix.outputs.exit_code != ''
219+
uses: actions/github-script@v7
220+
with:
221+
script: |
222+
const output = `${{ steps.execute.outputs.output || steps.fix.outputs.output }}`;
223+
let body = '[failed] Job failed.';
224+
if (output) {
225+
body += `\n\n<details><summary>Claude output (last 100 lines)</summary>\n\n\`\`\`\n${output}\n\`\`\`\n</details>`;
226+
}
227+
body += `\n\n[View full logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
228+
229+
await github.rest.issues.createComment({
230+
owner: context.repo.owner,
231+
repo: context.repo.repo,
232+
issue_number: context.issue.number,
233+
body: body
234+
});

0 commit comments

Comments
 (0)