Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 342 additions & 0 deletions .github/workflows/github-issue-autosolve-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
name: GitHub Issue Autosolve (Go)
on:
workflow_call:
inputs:
issue_number:
type: string
required: true
issue_title:
type: string
required: true
issue_body:
type: string
required: true
trigger_label:
type: string
required: false
default: "autosolve"
prompt:
type: string
required: false
default: ""
skill:
type: string
required: false
default: ""
additional_instructions:
type: string
required: false
default: ""
allowed_tools:
type: string
required: false
default: ""
model:
type: string
required: false
default: "claude-opus-4-6"
max_retries:
type: string
required: false
default: "3"
auth_mode:
type: string
required: false
default: "vertex"
vertex_project_id:
type: string
required: false
default: ""
vertex_region:
type: string
required: false
default: "us-east5"
vertex_workload_identity_provider:
type: string
required: false
default: ""
vertex_service_account:
type: string
required: false
default: ""
fork_owner:
type: string
required: true
fork_repo:
type: string
required: true
blocked_paths:
type: string
required: false
default: ".github/workflows/"
git_user_name:
type: string
required: false
default: "autosolve[bot]"
git_user_email:
type: string
required: false
default: "autosolve[bot]@users.noreply.github.com"
timeout_minutes:
type: number
required: false
default: 20
secrets:
repo_token:
required: true
fork_push_token:
required: true
pr_create_token:
required: true
anthropic_api_key:
required: false
outputs:
status:
value: ${{ jobs.solve.outputs.status }}
pr_url:
value: ${{ jobs.solve.outputs.pr_url }}

concurrency:
group: autosolve-issue-${{ inputs.issue_number }}
cancel-in-progress: false

env:
# Directory name for the target repo checkout (side-by-side layout).
REPO_DIR: repo

jobs:
solve:
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.timeout_minutes }}
permissions:
contents: read
issues: write
id-token: write
defaults:
run:
working-directory: ${{ env.REPO_DIR }}
outputs:
status: ${{ steps.final_status.outputs.status }}
pr_url: ${{ steps.implement.outputs.pr_url }}

steps:
# Side-by-side checkout: target repo in repo/, actions repo in actions/.
# Claude works in repo/ (via defaults.run.working-directory) so it cannot
# see or commit credential files or the actions checkout.
- name: Checkout target repo
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
path: ${{ env.REPO_DIR }}

# --- Check for existing PR ---
- name: Check for existing PR
id: check
shell: bash
run: |
pr_url="$(gh pr list --repo "$GITHUB_REPOSITORY" \
--label "autosolve-issue-${{ inputs.issue_number }}" \
--json url --jq '.[0].url // empty')"
exists=false
if [ -n "$pr_url" ]; then
exists=true
fi
echo "exists=$exists" >> "$GITHUB_OUTPUT"
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.pr_create_token }}

- name: Comment that PR already exists
if: steps.check.outputs.exists == 'true'
shell: bash
run: |
gh issue comment "${{ inputs.issue_number }}" \
--repo "$GITHUB_REPOSITORY" \
--body "Auto-solver was triggered but a PR already exists for this issue: $PR_URL

To create a new attempt, close the existing PR and add the label again."
env:
GH_TOKEN: ${{ secrets.repo_token }}
PR_URL: ${{ steps.check.outputs.pr_url }}

# --- Setup (skipped when PR already exists) ---
# Install Claude CLI BEFORE authentication so that npm install does
# not run with cloud credentials in the environment.
- name: Install Claude CLI
if: steps.check.outputs.exists != 'true'
shell: bash
run: |
npm install --global "@anthropic-ai/claude-code@2.1.79"
echo "Claude CLI installed: $(claude --version)"

- name: Authenticate to Google Cloud (Vertex)
if: steps.check.outputs.exists != 'true' && inputs.auth_mode == 'vertex' && inputs.vertex_workload_identity_provider != ''
uses: google-github-actions/auth@v3
with:
project_id: ${{ inputs.vertex_project_id }}
service_account: ${{ inputs.vertex_service_account }}
workload_identity_provider: ${{ inputs.vertex_workload_identity_provider }}

# --- Build prompt ---
- name: Build prompt from issue
if: steps.check.outputs.exists != 'true' && inputs.prompt == ''
shell: bash
run: |
{
echo "PROMPT<<END_OF_PROMPT"
printf 'Fix GitHub issue #%s.\n' "${{ inputs.issue_number }}"
printf 'Title: %s\n' "$ISSUE_TITLE"
printf 'Body: %s\n' "$ISSUE_BODY"
echo "END_OF_PROMPT"
} >> "$GITHUB_ENV"
env:
ISSUE_TITLE: ${{ inputs.issue_title }}
ISSUE_BODY: ${{ inputs.issue_body }}

- name: Set explicit prompt
if: steps.check.outputs.exists != 'true' && inputs.prompt != ''
shell: bash
run: |
{
echo "PROMPT<<END_OF_PROMPT"
printf '%s\n' "$INPUT_PROMPT"
echo "END_OF_PROMPT"
} >> "$GITHUB_ENV"
env:
INPUT_PROMPT: ${{ inputs.prompt }}

# --- Assess ---
- name: Assess
id: assess
if: steps.check.outputs.exists != 'true'
uses: cockroachdb/actions/autosolve-go/assess@autosolve-go-rewrite
env:
ANTHROPIC_API_KEY: ${{ inputs.auth_mode == 'api_key' && secrets.anthropic_api_key || '' }}
CLAUDE_CODE_USE_VERTEX: ${{ inputs.auth_mode == 'vertex' && '1' || '' }}
ANTHROPIC_VERTEX_PROJECT_ID: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_project_id || '' }}
CLOUD_ML_REGION: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_region || '' }}
with:
prompt: ${{ env.PROMPT }}
skill: ${{ inputs.skill }}
additional_instructions: ${{ inputs.additional_instructions }}
model: ${{ inputs.model }}
blocked_paths: ${{ inputs.blocked_paths }}
working_directory: ${{ env.REPO_DIR }}

- name: Comment that issue was skipped
if: steps.check.outputs.exists != 'true' && steps.assess.outputs.assessment == 'SKIP'
shell: bash
run: |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [actionlint] reported by reviewdog 🐶
shellcheck reported issue in this script: SC2016:info:1:16: Expressions don't expand in single quotes, use double quotes for that [shellcheck]

body="$(printf 'Auto-solver assessed this issue but determined it is not suitable for automated resolution.\n\n```\n%s\n```' "$SUMMARY")"
gh issue comment "${{ inputs.issue_number }}" \
--repo "$GITHUB_REPOSITORY" \
--body "$body"
env:
GH_TOKEN: ${{ secrets.repo_token }}
# Pass summary as an env var as a precautionary measure against
# command injection. It came from the summary claude generated so we
# expect it to be safe but let's be even safer.
SUMMARY: ${{ steps.assess.outputs.summary }}

# --- Implement ---
- name: Implement
id: implement
if: steps.check.outputs.exists != 'true' && steps.assess.outputs.assessment == 'PROCEED'
uses: cockroachdb/actions/autosolve-go/implement@autosolve-go-rewrite
env:
ANTHROPIC_API_KEY: ${{ inputs.auth_mode == 'api_key' && secrets.anthropic_api_key || '' }}
CLAUDE_CODE_USE_VERTEX: ${{ inputs.auth_mode == 'vertex' && '1' || '' }}
ANTHROPIC_VERTEX_PROJECT_ID: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_project_id || '' }}
CLOUD_ML_REGION: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_region || '' }}
with:
prompt: ${{ env.PROMPT }}
skill: ${{ inputs.skill }}
additional_instructions: ${{ inputs.additional_instructions }}
allowed_tools: ${{ inputs.allowed_tools }}
model: ${{ inputs.model }}
max_retries: ${{ inputs.max_retries }}
fork_owner: ${{ inputs.fork_owner }}
fork_repo: ${{ inputs.fork_repo }}
fork_push_token: ${{ secrets.fork_push_token }}
pr_create_token: ${{ secrets.pr_create_token }}
pr_labels: "autosolve,autosolve-issue-${{ inputs.issue_number }}"
pr_draft: "true"
blocked_paths: ${{ inputs.blocked_paths }}
git_user_name: ${{ inputs.git_user_name }}
git_user_email: ${{ inputs.git_user_email }}
branch_suffix: "issue-${{ inputs.issue_number }}"
working_directory: ${{ env.REPO_DIR }}

- name: Comment that implementation succeeded
if: steps.implement.outputs.status == 'SUCCESS'
shell: bash
run: |
gh issue comment "${{ inputs.issue_number }}" \
--repo "$GITHUB_REPOSITORY" \
--body "Auto-solver has created a draft PR: $PR_URL

Please review the changes carefully before approving."
env:
GH_TOKEN: ${{ secrets.repo_token }}
PR_URL: ${{ steps.implement.outputs.pr_url }}

- name: Comment that implementation failed
if: steps.assess.outputs.assessment == 'PROCEED' && steps.implement.outputs.status != 'SUCCESS'
shell: bash
run: |
gh issue comment "${{ inputs.issue_number }}" \
--repo "$GITHUB_REPOSITORY" \
--body "Auto-solver attempted to fix this issue but was unable to complete the implementation.

This issue may require human intervention."
env:
GH_TOKEN: ${{ secrets.repo_token }}

# --- Cleanup (always runs) ---
- name: Remove label
if: always()
shell: bash
# Best-effort: the label may already have been removed by another run.
run: gh issue edit "${{ inputs.issue_number }}" --repo "$GITHUB_REPOSITORY" --remove-label "${{ inputs.trigger_label }}" || true
env:
GH_TOKEN: ${{ secrets.repo_token }}

- name: Set final status
id: final_status
if: always()
shell: bash
run: |
if [ "$PR_EXISTS" = "true" ]; then
echo "status=EXISTING_PR" >> "$GITHUB_OUTPUT"
{
echo "## Autosolve"
echo "**Status:** Skipped — a PR already exists for this issue: $EXISTING_PR_URL"
echo ""
echo "To create a new attempt, close the existing PR and add the label again."
} >> "$GITHUB_STEP_SUMMARY"
elif [ "$ASSESSMENT" = "SKIP" ]; then
echo "status=SKIPPED" >> "$GITHUB_OUTPUT"
{
echo "## Autosolve"
echo "**Status:** Skipped — assessment determined this issue is not suitable for automated resolution."
} >> "$GITHUB_STEP_SUMMARY"
elif [ "$IMPL_STATUS" = "SUCCESS" ]; then
echo "status=SUCCESS" >> "$GITHUB_OUTPUT"
else
echo "status=FAILED" >> "$GITHUB_OUTPUT"
fi
env:
PR_EXISTS: ${{ steps.check.outputs.exists }}
EXISTING_PR_URL: ${{ steps.check.outputs.pr_url }}
ASSESSMENT: ${{ steps.assess.outputs.assessment }}
IMPL_STATUS: ${{ steps.implement.outputs.status }}

- name: Write token usage summary
if: always() && steps.check.outputs.exists != 'true'
shell: bash
run: |
f="$RUNNER_TEMP/autosolve-usage.md"
if [ -f "$f" ]; then
cat "$f" >> "$GITHUB_STEP_SUMMARY"
fi
32 changes: 26 additions & 6 deletions .github/workflows/github-issue-autosolve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ jobs:
ref: ${{ steps.actions_ref.outputs.ref }}
path: .actions

# Install Claude CLI BEFORE authentication so that npm install does
# not run with cloud credentials in the environment. The credentials
# file written by google-github-actions/auth contains a live OIDC
# bearer token, and npm post-install scripts could exfiltrate it.
- name: Install Claude CLI
shell: bash
run: |
npm install --global "@anthropic-ai/claude-code@2.1.79"
echo "Claude CLI installed: $(claude --version)"

- name: Authenticate to Google Cloud (Vertex)
if: inputs.auth_mode == 'vertex' && inputs.vertex_workload_identity_provider != ''
uses: google-github-actions/auth@v3
Expand All @@ -203,6 +213,22 @@ jobs:
service_account: ${{ inputs.vertex_service_account }}
workload_identity_provider: ${{ inputs.vertex_workload_identity_provider }}

# Move the credentials file out of $GITHUB_WORKSPACE so that Claude
# cannot read or accidentally commit it.
- name: Move credentials out of workspace
if: inputs.auth_mode == 'vertex' && inputs.vertex_workload_identity_provider != ''
shell: bash
run: |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [actionlint] reported by reviewdog 🐶
shellcheck reported issue in this script: SC2129:style:6:3: Consider using { cmd1; cmd2; } >> file instead of individual redirects [shellcheck]

for f in gha-creds-*.json; do
[ -f "$f" ] && mv "$f" "$RUNNER_TEMP/" && echo "Moved $f to RUNNER_TEMP"
done
if [ -n "$GOOGLE_APPLICATION_CREDENTIALS" ] && [[ "$GOOGLE_APPLICATION_CREDENTIALS" == "$GITHUB_WORKSPACE"* ]]; then
new_path="$RUNNER_TEMP/$(basename "$GOOGLE_APPLICATION_CREDENTIALS")"
echo "GOOGLE_APPLICATION_CREDENTIALS=$new_path" >> "$GITHUB_ENV"
echo "CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=$new_path" >> "$GITHUB_ENV"
echo "GOOGLE_GHA_CREDS_PATH=$new_path" >> "$GITHUB_ENV"
fi

# --- Build prompt ---
- name: Build prompt
id: build_prompt
Expand Down Expand Up @@ -231,12 +257,6 @@ jobs:
ANTHROPIC_VERTEX_PROJECT_ID: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_project_id || '' }}
CLOUD_ML_REGION: ${{ inputs.auth_mode == 'vertex' && inputs.vertex_region || '' }}

- name: Install Claude CLI
shell: bash
run: ${{ env.ACTIONS_DIR }}/run_step.sh shared install_claude
env:
CLAUDE_CLI_VERSION: "2.1.79"

- name: Build assessment prompt
id: assess_prompt
shell: bash
Expand Down
Loading
Loading