Skip to content

Commit 5d57675

Browse files
committed
Update pr-automation workflow with PR creation trigger and two-job architecture
1 parent 4a90b63 commit 5d57675

1 file changed

Lines changed: 144 additions & 46 deletions

File tree

.github/workflows/pr-automation.yml

Lines changed: 144 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,97 @@ name: PR Automation
33
on:
44
issue_comment:
55
types: [created]
6+
pull_request:
7+
types: [opened]
68

79
jobs:
8-
execute:
9-
# Only run on PR comments with recognized commands
10+
# First job runs on GitHub-hosted runner to set pending status immediately
11+
setup:
12+
# Run on PR comments with commands OR PR creation with commands in body
1013
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]'))
14+
(github.event_name == 'issue_comment' &&
15+
github.event.issue.pull_request &&
16+
(startsWith(github.event.comment.body, '[action]') ||
17+
startsWith(github.event.comment.body, '[fix]') ||
18+
startsWith(github.event.comment.body, '[debug]'))) ||
19+
(github.event_name == 'pull_request' &&
20+
(contains(github.event.pull_request.body, '[action]') ||
21+
contains(github.event.pull_request.body, '[fix]') ||
22+
contains(github.event.pull_request.body, '[debug]')))
1523
16-
runs-on: ${{ vars.RUNNER_TYPE || 'ubuntu-latest' }}
24+
runs-on: ubuntu-latest
1725

1826
permissions:
19-
contents: write
20-
pull-requests: write
2127
statuses: write
28+
pull-requests: read
29+
30+
outputs:
31+
branch: ${{ steps.pr.outputs.branch }}
32+
sha: ${{ steps.pr.outputs.sha }}
33+
pr_number: ${{ steps.pr.outputs.pr_number }}
34+
command: ${{ steps.pr.outputs.command }}
35+
instructions: ${{ steps.pr.outputs.instructions }}
2236

2337
steps:
2438
- name: Get PR details and set pending status
2539
id: pr
2640
uses: actions/github-script@v7
2741
with:
2842
script: |
29-
const pr = await github.rest.pulls.get({
30-
owner: context.repo.owner,
31-
repo: context.repo.repo,
32-
pull_number: context.issue.number
33-
});
34-
const sha = pr.data.head.sha;
35-
core.setOutput('branch', pr.data.head.ref);
43+
let prData, body, prNumber;
44+
45+
if (context.eventName === 'pull_request') {
46+
// PR creation event - data is directly available
47+
prData = context.payload.pull_request;
48+
body = prData.body || '';
49+
prNumber = prData.number;
50+
} else {
51+
// Comment event - need to fetch PR data
52+
const pr = await github.rest.pulls.get({
53+
owner: context.repo.owner,
54+
repo: context.repo.repo,
55+
pull_number: context.issue.number
56+
});
57+
prData = pr.data;
58+
body = context.payload.comment.body;
59+
prNumber = context.issue.number;
60+
}
61+
62+
const sha = prData.head.sha;
63+
core.setOutput('branch', prData.head.ref);
3664
core.setOutput('sha', sha);
65+
core.setOutput('pr_number', prNumber);
3766
38-
const body = context.payload.comment.body;
67+
// Extract command - for PR body, search anywhere; for comments, must start with command
3968
let command = 'unknown';
4069
let instructions = '';
41-
if (body.startsWith('[action]')) {
42-
command = 'action';
43-
instructions = body.slice('[action]'.length).trim();
44-
} else if (body.startsWith('[fix]')) {
45-
command = 'fix';
46-
instructions = body.slice('[fix]'.length).trim();
47-
} else if (body.startsWith('[debug]')) {
48-
command = 'debug';
70+
71+
if (context.eventName === 'pull_request') {
72+
// For PR body, find command anywhere in text
73+
if (body.includes('[action]')) {
74+
command = 'action';
75+
const match = body.match(/\[action\](.*?)(?=\[(?:action|fix|debug)\]|$)/s);
76+
instructions = match ? match[1].trim() : '';
77+
} else if (body.includes('[fix]')) {
78+
command = 'fix';
79+
const match = body.match(/\[fix\](.*?)(?=\[(?:action|fix|debug)\]|$)/s);
80+
instructions = match ? match[1].trim() : '';
81+
} else if (body.includes('[debug]')) {
82+
command = 'debug';
83+
}
84+
} else {
85+
// For comments, must start with command
86+
if (body.startsWith('[action]')) {
87+
command = 'action';
88+
instructions = body.slice('[action]'.length).trim();
89+
} else if (body.startsWith('[fix]')) {
90+
command = 'fix';
91+
instructions = body.slice('[fix]'.length).trim();
92+
} else if (body.startsWith('[debug]')) {
93+
command = 'debug';
94+
}
4995
}
96+
5097
core.setOutput('command', command);
5198
core.setOutput('instructions', instructions);
5299
@@ -57,14 +104,38 @@ jobs:
57104
sha: sha,
58105
state: 'pending',
59106
context: `PR Automation / ${command}`,
60-
description: 'Running...',
107+
description: 'Waiting for runner...',
61108
target_url: `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
62109
});
63110
111+
execute:
112+
needs: setup
113+
runs-on: ${{ vars.RUNNER_TYPE || 'ubuntu-latest' }}
114+
115+
permissions:
116+
contents: write
117+
pull-requests: write
118+
statuses: write
119+
120+
steps:
121+
- name: Update status to running
122+
uses: actions/github-script@v7
123+
with:
124+
script: |
125+
await github.rest.repos.createCommitStatus({
126+
owner: context.repo.owner,
127+
repo: context.repo.repo,
128+
sha: '${{ needs.setup.outputs.sha }}',
129+
state: 'pending',
130+
context: 'PR Automation / ${{ needs.setup.outputs.command }}',
131+
description: 'Running...',
132+
target_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
133+
});
134+
64135
- name: Checkout PR branch
65136
uses: actions/checkout@v4
66137
with:
67-
ref: ${{ steps.pr.outputs.branch }}
138+
ref: ${{ needs.setup.outputs.branch }}
68139
fetch-depth: 0
69140

70141
- name: Setup Node.js (GitHub-hosted only)
@@ -83,7 +154,7 @@ jobs:
83154

84155
- name: Find plan file
85156
id: plan
86-
if: steps.pr.outputs.command == 'action'
157+
if: needs.setup.outputs.command == 'action'
87158
run: |
88159
for f in PLAN.md plan.md .claude/plan.md docs/plan.md; do
89160
[ -f "$f" ] && echo "file=$f" >> $GITHUB_OUTPUT && exit 0
@@ -93,14 +164,22 @@ jobs:
93164
echo "No plan file found" && exit 1
94165
95166
- name: Execute plan
96-
if: steps.pr.outputs.command == 'action'
167+
if: needs.setup.outputs.command == 'action'
97168
env:
98169
ANTHROPIC_API_KEY_SECRET: ${{ secrets.ANTHROPIC_API_KEY }}
99170
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
100171
run: |
172+
set -eo pipefail
101173
# Use secret if set, otherwise use runner's env
102174
[ -n "$ANTHROPIC_API_KEY_SECRET" ] && export ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY_SECRET"
103-
INSTRUCTIONS='${{ steps.pr.outputs.instructions }}'
175+
176+
# Check API key is available
177+
if [ -z "$ANTHROPIC_API_KEY" ]; then
178+
echo "Error: ANTHROPIC_API_KEY not set" | tee claude-output.txt
179+
exit 1
180+
fi
181+
182+
INSTRUCTIONS='${{ needs.setup.outputs.instructions }}'
104183
claude --dangerously-skip-permissions --max-turns 100 -p "
105184
Execute the plan in '${{ steps.plan.outputs.file }}'.
106185
${INSTRUCTIONS:+
@@ -110,32 +189,45 @@ jobs:
110189
## Process
111190
1. Read the plan file
112191
2. Use /subagent-driven-development to execute tasks
113-
3. Push: git push origin ${{ steps.pr.outputs.branch }}
114-
4. Post summary: gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body 'SUMMARY'
192+
3. Push: git push origin ${{ needs.setup.outputs.branch }}
193+
4. Post summary: gh pr comment ${{ needs.setup.outputs.pr_number }} --repo ${{ github.repository }} --body 'SUMMARY'
115194
116195
## Rules
117196
- Tests should be strong enough to catch regressions.
118197
- Do not modify tests to make them pass.
119198
- Test failure must be reported.
120199
" 2>&1 | tee claude-output.txt
121200
201+
# Check for authentication errors in output
202+
if grep -qiE "authenticat(e|ion)|unauthorized|forbidden|invalid.*key|api.*key.*invalid|API Error: 40[13]" claude-output.txt; then
203+
echo "Error: Authentication failure detected"
204+
exit 1
205+
fi
206+
122207
- name: Fix issues
123-
if: steps.pr.outputs.command == 'fix'
208+
if: needs.setup.outputs.command == 'fix'
124209
env:
125210
ANTHROPIC_API_KEY_SECRET: ${{ secrets.ANTHROPIC_API_KEY }}
126211
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
127212
run: |
213+
set -eo pipefail
128214
# Use secret if set, otherwise use runner's env
129215
[ -n "$ANTHROPIC_API_KEY_SECRET" ] && export ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY_SECRET"
130216
217+
# Check API key is available
218+
if [ -z "$ANTHROPIC_API_KEY" ]; then
219+
echo "Error: ANTHROPIC_API_KEY not set" | tee claude-output.txt
220+
exit 1
221+
fi
222+
131223
# Gather all feedback sources
132-
INLINE=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/comments --jq '.[].body' 2>/dev/null || echo "")
133-
REVIEWS=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/reviews --jq '.[] | select(.body != "") | .body' 2>/dev/null || echo "")
134-
PR_COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[].body' 2>/dev/null || echo "")
224+
INLINE=$(gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/comments --jq '.[].body' 2>/dev/null || echo "")
225+
REVIEWS=$(gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/reviews --jq '.[] | select(.body != "") | .body' 2>/dev/null || echo "")
226+
PR_COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ needs.setup.outputs.pr_number }}/comments --jq '.[].body' 2>/dev/null || echo "")
135227
136228
# Gather CI status
137-
CI_STATUS=$(gh pr checks ${{ github.event.issue.number }} --repo ${{ github.repository }} 2>/dev/null || echo "")
138-
FAILED_RUNS=$(gh run list --branch ${{ steps.pr.outputs.branch }} --status failure --limit 3 --json databaseId,name --jq '.[] | "\(.databaseId) \(.name)"' 2>/dev/null || echo "")
229+
CI_STATUS=$(gh pr checks ${{ needs.setup.outputs.pr_number }} --repo ${{ github.repository }} 2>/dev/null || echo "")
230+
FAILED_RUNS=$(gh run list --branch ${{ needs.setup.outputs.branch }} --status failure --limit 3 --json databaseId,name --jq '.[] | "\(.databaseId) \(.name)"' 2>/dev/null || echo "")
139231
140232
# Get failed run logs if any
141233
CI_LOGS=""
@@ -145,7 +237,7 @@ jobs:
145237
$(gh run view $run_id --log-failed 2>/dev/null | tail -100 || echo 'Could not fetch logs')"
146238
done
147239
148-
INSTRUCTIONS='${{ steps.pr.outputs.instructions }}'
240+
INSTRUCTIONS='${{ needs.setup.outputs.instructions }}'
149241
claude --dangerously-skip-permissions --max-turns 100 -p "
150242
Fix all issues with this PR: review comments AND CI failures.
151243
${INSTRUCTIONS:+
@@ -172,16 +264,22 @@ jobs:
172264
2. Fix review comments and CI issues
173265
3. Run tests to verify: make test, cargo test, npm test, etc.
174266
4. Commit: git commit -am 'Fix review feedback and CI issues'
175-
5. Push: git push origin ${{ steps.pr.outputs.branch }}
176-
6. Post summary: gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body 'SUMMARY'
267+
5. Push: git push origin ${{ needs.setup.outputs.branch }}
268+
6. Post summary: gh pr comment ${{ needs.setup.outputs.pr_number }} --repo ${{ github.repository }} --body 'SUMMARY'
177269
178270
## Rules
179271
- All CI failures must be fixed.
180272
- All change requests must be either addressed or explained.
181273
" 2>&1 | tee claude-output.txt
182274
275+
# Check for authentication errors in output
276+
if grep -qiE "authenticat(e|ion)|unauthorized|forbidden|invalid.*key|api.*key.*invalid|API Error: 40[13]" claude-output.txt; then
277+
echo "Error: Authentication failure detected"
278+
exit 1
279+
fi
280+
183281
- name: Debug test
184-
if: steps.pr.outputs.command == 'debug'
282+
if: needs.setup.outputs.command == 'debug'
185283
run: |
186284
echo "Debug test passed - workflow is working" | tee claude-output.txt
187285
@@ -201,9 +299,9 @@ jobs:
201299
await github.rest.repos.createCommitStatus({
202300
owner: context.repo.owner,
203301
repo: context.repo.repo,
204-
sha: '${{ steps.pr.outputs.sha }}',
302+
sha: '${{ needs.setup.outputs.sha }}',
205303
state: 'success',
206-
context: 'PR Automation / ${{ steps.pr.outputs.command }}',
304+
context: 'PR Automation / ${{ needs.setup.outputs.command }}',
207305
description: 'Completed successfully',
208306
target_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
209307
});
@@ -216,9 +314,9 @@ jobs:
216314
await github.rest.repos.createCommitStatus({
217315
owner: context.repo.owner,
218316
repo: context.repo.repo,
219-
sha: '${{ steps.pr.outputs.sha }}',
317+
sha: '${{ needs.setup.outputs.sha }}',
220318
state: 'failure',
221-
context: 'PR Automation / ${{ steps.pr.outputs.command }}',
319+
context: 'PR Automation / ${{ needs.setup.outputs.command }}',
222320
description: 'Failed - check logs',
223321
target_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
224322
});

0 commit comments

Comments
 (0)