-
Notifications
You must be signed in to change notification settings - Fork 2
Add Go rewrite of autosolve actions #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
fantapop
wants to merge
17
commits into
CNSL-1944-generic-autosolve-git-hub-workflow-for-automated-issue-resolution
from
autosolve-go-rewrite
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d4a965e
Add Go rewrite of autosolve actions
fantapop 417d4b9
Add security hardening to bash autosolve implementation
fantapop 3f51ac4
Add silent error handling convention to CLAUDE.md
fantapop d832bb0
Precompile binary, simplify workflow, improve security review and usa…
fantapop 9ce80c6
Fix YAML parsing error in action.yml run commands
fantapop a8e001d
Share usage tracking across assess and implement steps
fantapop 2f17a68
Fix backtick command injection in workflow shell steps
fantapop 9a78524
Add step summary for skipped/existing-PR cases
fantapop dfe5f88
Send git stderr to os.Stderr for better error diagnostics
fantapop cf3d809
Fix working directory for composite actions in side-by-side layout
fantapop 876a6a0
Move token usage summary to a separate final step
fantapop 989fae5
Convert usage-summary to composite action
fantapop f535dff
Fix security review model and CLI args
fantapop c5fb4fa
Simplify usage summary: render markdown in Save(), cat in workflow
fantapop f36f81c
Use claude-sonnet-4-6 for security review
fantapop b83157f
Drop JSON usage file; persist usage as markdown only
fantapop dce90e9
Upgrade actions/setup-go@v5 to @v6
fantapop File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | | ||
| 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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: | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [actionlint] reported by reviewdog 🐶 |
||
| 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 | ||
|
|
@@ -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 | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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]