Skip to content

Commit f97d11e

Browse files
Add github-issue-autosolve reusable workflow
Turnkey reusable workflow for GitHub Issues integration that composes the autosolve/assess and autosolve/implement actions with: - Automatic issue comments for status updates (PR created, skipped, failed, already exists) - Label-based triggering with automatic label removal - Concurrency control per issue number - Step summaries for skipped/existing-PR cases - Token usage summary appended to step summary - Side-by-side checkout layout isolating credentials from Claude - Vertex AI and API key authentication modes Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
1 parent a9a9010 commit f97d11e

2 files changed

Lines changed: 345 additions & 0 deletions

File tree

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
name: GitHub Issue Autosolve
2+
on:
3+
workflow_call:
4+
inputs:
5+
issue_number:
6+
type: string
7+
required: true
8+
issue_title:
9+
type: string
10+
required: true
11+
issue_body:
12+
type: string
13+
required: true
14+
trigger_label:
15+
type: string
16+
required: false
17+
default: "autosolve"
18+
prompt:
19+
type: string
20+
required: false
21+
default: ""
22+
skill:
23+
type: string
24+
required: false
25+
default: ""
26+
additional_instructions:
27+
type: string
28+
required: false
29+
default: ""
30+
allowed_tools:
31+
type: string
32+
required: false
33+
default: ""
34+
model:
35+
type: string
36+
required: false
37+
default: "claude-opus-4-6"
38+
max_retries:
39+
type: string
40+
required: false
41+
default: "3"
42+
auth_mode:
43+
type: string
44+
required: false
45+
default: "vertex"
46+
vertex_project_id:
47+
type: string
48+
required: false
49+
default: ""
50+
vertex_region:
51+
type: string
52+
required: false
53+
default: "us-east5"
54+
vertex_workload_identity_provider:
55+
type: string
56+
required: false
57+
default: ""
58+
vertex_service_account:
59+
type: string
60+
required: false
61+
default: ""
62+
fork_owner:
63+
type: string
64+
required: true
65+
fork_repo:
66+
type: string
67+
required: true
68+
blocked_paths:
69+
type: string
70+
required: false
71+
default: ".github/workflows/"
72+
git_user_name:
73+
type: string
74+
required: false
75+
default: "autosolve[bot]"
76+
git_user_email:
77+
type: string
78+
required: false
79+
default: "autosolve[bot]@users.noreply.github.com"
80+
timeout_minutes:
81+
type: number
82+
required: false
83+
default: 20
84+
secrets:
85+
repo_token:
86+
required: true
87+
fork_push_token:
88+
required: true
89+
pr_create_token:
90+
required: true
91+
anthropic_api_key:
92+
required: false
93+
outputs:
94+
status:
95+
value: ${{ jobs.solve.outputs.status }}
96+
pr_url:
97+
value: ${{ jobs.solve.outputs.pr_url }}
98+
99+
concurrency:
100+
group: autosolve-issue-${{ inputs.issue_number }}
101+
cancel-in-progress: false
102+
103+
env:
104+
# Directory name for the target repo checkout (side-by-side layout).
105+
REPO_DIR: repo
106+
107+
jobs:
108+
solve:
109+
runs-on: ubuntu-latest
110+
timeout-minutes: ${{ inputs.timeout_minutes }}
111+
permissions:
112+
contents: read
113+
issues: write
114+
id-token: write
115+
defaults:
116+
run:
117+
working-directory: ${{ env.REPO_DIR }}
118+
outputs:
119+
status: ${{ steps.final_status.outputs.status }}
120+
pr_url: ${{ steps.implement.outputs.pr_url }}
121+
122+
steps:
123+
# Side-by-side checkout: target repo in repo/, actions repo in actions/.
124+
# Claude works in repo/ (via defaults.run.working-directory) so it cannot
125+
# see or commit credential files or the actions checkout.
126+
- name: Checkout target repo
127+
uses: actions/checkout@v5
128+
with:
129+
fetch-depth: 0
130+
persist-credentials: false
131+
path: ${{ env.REPO_DIR }}
132+
133+
# --- Check for existing PR ---
134+
- name: Check for existing PR
135+
id: check
136+
shell: bash
137+
run: |
138+
pr_url="$(gh pr list --repo "$GITHUB_REPOSITORY" \
139+
--label "autosolve-issue-${{ inputs.issue_number }}" \
140+
--json url --jq '.[0].url // empty')"
141+
exists=false
142+
if [ -n "$pr_url" ]; then
143+
exists=true
144+
fi
145+
echo "exists=$exists" >> "$GITHUB_OUTPUT"
146+
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
147+
env:
148+
GH_TOKEN: ${{ secrets.pr_create_token }}
149+
150+
- name: Comment that PR already exists
151+
if: steps.check.outputs.exists == 'true'
152+
shell: bash
153+
run: |
154+
gh issue comment "${{ inputs.issue_number }}" \
155+
--repo "$GITHUB_REPOSITORY" \
156+
--body "Auto-solver was triggered but a PR already exists for this issue: $PR_URL
157+
158+
To create a new attempt, close the existing PR and add the label again."
159+
env:
160+
GH_TOKEN: ${{ secrets.repo_token }}
161+
PR_URL: ${{ steps.check.outputs.pr_url }}
162+
163+
# --- Setup (skipped when PR already exists) ---
164+
# Install Claude CLI BEFORE authentication so that npm install does
165+
# not run with cloud credentials in the environment.
166+
- name: Install Claude CLI
167+
if: steps.check.outputs.exists != 'true'
168+
shell: bash
169+
run: |
170+
npm install --global "@anthropic-ai/claude-code@2.1.79"
171+
echo "Claude CLI installed: $(claude --version)"
172+
173+
- name: Authenticate to Google Cloud (Vertex)
174+
if: steps.check.outputs.exists != 'true' && inputs.auth_mode == 'vertex' && inputs.vertex_workload_identity_provider != ''
175+
uses: google-github-actions/auth@v3
176+
with:
177+
project_id: ${{ inputs.vertex_project_id }}
178+
service_account: ${{ inputs.vertex_service_account }}
179+
workload_identity_provider: ${{ inputs.vertex_workload_identity_provider }}
180+
181+
# --- Build prompt ---
182+
- name: Build prompt from issue
183+
if: steps.check.outputs.exists != 'true' && inputs.prompt == ''
184+
shell: bash
185+
run: |
186+
{
187+
echo "PROMPT<<END_OF_PROMPT"
188+
printf 'Fix GitHub issue #%s.\n' "${{ inputs.issue_number }}"
189+
printf 'Title: %s\n' "$ISSUE_TITLE"
190+
printf 'Body: %s\n' "$ISSUE_BODY"
191+
echo "END_OF_PROMPT"
192+
} >> "$GITHUB_ENV"
193+
env:
194+
ISSUE_TITLE: ${{ inputs.issue_title }}
195+
ISSUE_BODY: ${{ inputs.issue_body }}
196+
197+
- name: Set explicit prompt
198+
if: steps.check.outputs.exists != 'true' && inputs.prompt != ''
199+
shell: bash
200+
run: |
201+
{
202+
echo "PROMPT<<END_OF_PROMPT"
203+
printf '%s\n' "$INPUT_PROMPT"
204+
echo "END_OF_PROMPT"
205+
} >> "$GITHUB_ENV"
206+
env:
207+
INPUT_PROMPT: ${{ inputs.prompt }}
208+
209+
# --- Assess ---
210+
- name: Assess
211+
id: assess
212+
if: steps.check.outputs.exists != 'true'
213+
uses: cockroachdb/actions/autosolve/assess@main
214+
env:
215+
ANTHROPIC_API_KEY: ${{ inputs.auth_mode == 'api_key' && secrets.anthropic_api_key || '' }}
216+
CLAUDE_CODE_USE_VERTEX: ${{ inputs.auth_mode == 'vertex' && '1' || '' }}
217+
ANTHROPIC_VERTEX_PROJECT_ID: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_project_id || '' }}
218+
CLOUD_ML_REGION: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_region || '' }}
219+
with:
220+
prompt: ${{ env.PROMPT }}
221+
skill: ${{ inputs.skill }}
222+
additional_instructions: ${{ inputs.additional_instructions }}
223+
model: ${{ inputs.model }}
224+
blocked_paths: ${{ inputs.blocked_paths }}
225+
working_directory: ${{ env.REPO_DIR }}
226+
227+
- name: Comment that issue was skipped
228+
if: steps.check.outputs.exists != 'true' && steps.assess.outputs.assessment == 'SKIP'
229+
shell: bash
230+
run: |
231+
body="$(printf 'Auto-solver assessed this issue but determined it is not suitable for automated resolution.\n\n```\n%s\n```' "$SUMMARY")"
232+
gh issue comment "${{ inputs.issue_number }}" \
233+
--repo "$GITHUB_REPOSITORY" \
234+
--body "$body"
235+
env:
236+
GH_TOKEN: ${{ secrets.repo_token }}
237+
# Pass summary as an env var as a precautionary measure against
238+
# command injection. It came from the summary claude generated so we
239+
# expect it to be safe but let's be even safer.
240+
SUMMARY: ${{ steps.assess.outputs.summary }}
241+
242+
# --- Implement ---
243+
- name: Implement
244+
id: implement
245+
if: steps.check.outputs.exists != 'true' && steps.assess.outputs.assessment == 'PROCEED'
246+
uses: cockroachdb/actions/autosolve/implement@main
247+
env:
248+
ANTHROPIC_API_KEY: ${{ inputs.auth_mode == 'api_key' && secrets.anthropic_api_key || '' }}
249+
CLAUDE_CODE_USE_VERTEX: ${{ inputs.auth_mode == 'vertex' && '1' || '' }}
250+
ANTHROPIC_VERTEX_PROJECT_ID: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_project_id || '' }}
251+
CLOUD_ML_REGION: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_region || '' }}
252+
with:
253+
prompt: ${{ env.PROMPT }}
254+
skill: ${{ inputs.skill }}
255+
additional_instructions: ${{ inputs.additional_instructions }}
256+
allowed_tools: ${{ inputs.allowed_tools }}
257+
model: ${{ inputs.model }}
258+
max_retries: ${{ inputs.max_retries }}
259+
fork_owner: ${{ inputs.fork_owner }}
260+
fork_repo: ${{ inputs.fork_repo }}
261+
fork_push_token: ${{ secrets.fork_push_token }}
262+
pr_create_token: ${{ secrets.pr_create_token }}
263+
pr_labels: "autosolve,autosolve-issue-${{ inputs.issue_number }}"
264+
pr_draft: "true"
265+
blocked_paths: ${{ inputs.blocked_paths }}
266+
git_user_name: ${{ inputs.git_user_name }}
267+
git_user_email: ${{ inputs.git_user_email }}
268+
branch_suffix: "issue-${{ inputs.issue_number }}"
269+
working_directory: ${{ env.REPO_DIR }}
270+
271+
- name: Comment that implementation succeeded
272+
if: steps.implement.outputs.status == 'SUCCESS'
273+
shell: bash
274+
run: |
275+
gh issue comment "${{ inputs.issue_number }}" \
276+
--repo "$GITHUB_REPOSITORY" \
277+
--body "Auto-solver has created a draft PR: $PR_URL
278+
279+
Please review the changes carefully before approving."
280+
env:
281+
GH_TOKEN: ${{ secrets.repo_token }}
282+
PR_URL: ${{ steps.implement.outputs.pr_url }}
283+
284+
- name: Comment that implementation failed
285+
if: steps.assess.outputs.assessment == 'PROCEED' && steps.implement.outputs.status != 'SUCCESS'
286+
shell: bash
287+
run: |
288+
gh issue comment "${{ inputs.issue_number }}" \
289+
--repo "$GITHUB_REPOSITORY" \
290+
--body "Auto-solver attempted to fix this issue but was unable to complete the implementation.
291+
292+
This issue may require human intervention."
293+
env:
294+
GH_TOKEN: ${{ secrets.repo_token }}
295+
296+
# --- Cleanup (always runs) ---
297+
- name: Remove label
298+
if: always()
299+
shell: bash
300+
# Best-effort: the label may already have been removed by another run.
301+
run: gh issue edit "${{ inputs.issue_number }}" --repo "$GITHUB_REPOSITORY" --remove-label "${{ inputs.trigger_label }}" || true
302+
env:
303+
GH_TOKEN: ${{ secrets.repo_token }}
304+
305+
- name: Set final status
306+
id: final_status
307+
if: always()
308+
shell: bash
309+
run: |
310+
if [ "$PR_EXISTS" = "true" ]; then
311+
echo "status=EXISTING_PR" >> "$GITHUB_OUTPUT"
312+
{
313+
echo "## Autosolve"
314+
echo "**Status:** Skipped — a PR already exists for this issue: $EXISTING_PR_URL"
315+
echo ""
316+
echo "To create a new attempt, close the existing PR and add the label again."
317+
} >> "$GITHUB_STEP_SUMMARY"
318+
elif [ "$ASSESSMENT" = "SKIP" ]; then
319+
echo "status=SKIPPED" >> "$GITHUB_OUTPUT"
320+
{
321+
echo "## Autosolve"
322+
echo "**Status:** Skipped — assessment determined this issue is not suitable for automated resolution."
323+
} >> "$GITHUB_STEP_SUMMARY"
324+
elif [ "$IMPL_STATUS" = "SUCCESS" ]; then
325+
echo "status=SUCCESS" >> "$GITHUB_OUTPUT"
326+
else
327+
echo "status=FAILED" >> "$GITHUB_OUTPUT"
328+
fi
329+
env:
330+
PR_EXISTS: ${{ steps.check.outputs.exists }}
331+
EXISTING_PR_URL: ${{ steps.check.outputs.pr_url }}
332+
ASSESSMENT: ${{ steps.assess.outputs.assessment }}
333+
IMPL_STATUS: ${{ steps.implement.outputs.status }}
334+
335+
- name: Write token usage summary
336+
if: always() && steps.check.outputs.exists != 'true'
337+
shell: bash
338+
run: |
339+
f="$RUNNER_TEMP/autosolve-usage.md"
340+
if [ -f "$f" ]; then
341+
cat "$f" >> "$GITHUB_STEP_SUMMARY"
342+
fi

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Breaking changes are prefixed with "Breaking Change: ".
1010

1111
### Added
1212

13+
- `github-issue-autosolve` reusable workflow: turnkey GitHub Issues
14+
integration composing assess + implement with issue comments, label
15+
management, and concurrency control.
1316
- `autosolve/assess` action: evaluate tasks for automated resolution suitability
1417
using Claude in read-only mode.
1518
- `autosolve/implement` action: autonomously implement solutions, validate

0 commit comments

Comments
 (0)