Skip to content

Commit 6f1121d

Browse files
Add autosolve actions for automated issue resolution
Go implementation of composite actions for Claude-powered automated issue resolution: - `autosolve/assess`: Runs Claude in read-only mode to evaluate whether a task is suitable for automated resolution. - `autosolve/implement`: Runs Claude to implement a solution, validates changes against blocked paths, runs an AI security review of staged diffs, pushes to a fork, and creates a draft PR. Key features: - Precompiled Go binary (no Go toolchain needed at runtime) - Per-file batched AI security review with generated-file detection - Token usage tracking across phases with combined markdown summary - Retry logic with Claude session resumption - Skill file support for custom prompts Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
1 parent 817680c commit 6f1121d

30 files changed

Lines changed: 3617 additions & 1 deletion

.github/workflows/test.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,19 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v5
11-
- run: ./test.sh
11+
- uses: actions/setup-go@v6
12+
with:
13+
go-version-file: autosolve/go.mod
14+
- name: Run shell tests
15+
run: ./test.sh
16+
- name: Run Go tests
17+
run: cd autosolve-go && go test ./... -count=1
18+
- name: Check precompiled binary is up to date
19+
run: |
20+
cd autosolve-go
21+
./build.sh
22+
if ! git diff --quiet --exit-code bin/; then
23+
echo "::error::Precompiled binary is stale. Run ./autosolve/build.sh and commit the result."
24+
git diff --stat bin/
25+
exit 1
26+
fi

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ Breaking changes are prefixed with "Breaking Change: ".
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- `autosolve/assess` action: evaluate tasks for automated resolution suitability
14+
using Claude in read-only mode.
15+
- `autosolve/implement` action: autonomously implement solutions, validate
16+
security, push to fork, and create PRs using Claude. Includes AI security
17+
review, token usage tracking, and per-file batched diff analysis.
18+
1119
## [0.1.0] - 2026-03-23
1220

1321
### Added

autosolve/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Local dev binary (built by `make build` or `go build`)
2+
/autosolve

autosolve/Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.PHONY: build build-linux test clean
2+
3+
# Local dev binary
4+
build:
5+
go build -o autosolve ./cmd/autosolve
6+
7+
# Cross-compile for GitHub Actions (linux/amd64), same as build.sh
8+
build-linux:
9+
./build.sh
10+
11+
test:
12+
go test ./... -count=1
13+
14+
clean:
15+
rm -f autosolve bin/autosolve-linux-amd64

autosolve/assess/action.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Autosolve Assess
2+
description: Run Claude in read-only mode to assess whether a task is suitable for automated resolution.
3+
4+
# Prerequisites:
5+
# The calling workflow should install the Claude CLI BEFORE authenticating
6+
# to any cloud provider. npm post-install scripts run with the job's
7+
# full environment, so installing after auth would expose credentials
8+
# (e.g. the OIDC bearer token in gha-creds-*.json) to arbitrary code.
9+
10+
inputs:
11+
prompt:
12+
description: The task to assess. Plain text instructions describing what needs to be done.
13+
required: false
14+
default: ""
15+
skill:
16+
description: Path to a skill/prompt file relative to the repo root.
17+
required: false
18+
default: ""
19+
additional_instructions:
20+
description: Extra context appended after the task prompt but before the assessment footer.
21+
required: false
22+
default: ""
23+
assessment_criteria:
24+
description: Custom criteria for the assessment. If not provided, uses default criteria.
25+
required: false
26+
default: ""
27+
model:
28+
description: Claude model ID.
29+
required: false
30+
default: "claude-opus-4-6"
31+
blocked_paths:
32+
description: Comma-separated path prefixes that cannot be modified (injected into security preamble).
33+
required: false
34+
default: ".github/workflows/"
35+
working_directory:
36+
description: Directory to run in (relative to workspace root). Defaults to workspace root.
37+
required: false
38+
default: "."
39+
40+
outputs:
41+
assessment:
42+
description: PROCEED or SKIP
43+
value: ${{ steps.assess.outputs.assessment }}
44+
summary:
45+
description: Human-readable assessment reasoning.
46+
value: ${{ steps.assess.outputs.summary }}
47+
result:
48+
description: Full Claude result text.
49+
value: ${{ steps.assess.outputs.result }}
50+
51+
runs:
52+
using: "composite"
53+
steps:
54+
- name: Run assessment
55+
id: assess
56+
shell: bash
57+
working-directory: ${{ inputs.working_directory }}
58+
run: ${{ github.action_path }}/../bin/autosolve-linux-amd64 assess
59+
env:
60+
INPUT_PROMPT: ${{ inputs.prompt }}
61+
INPUT_SKILL: ${{ inputs.skill }}
62+
INPUT_ADDITIONAL_INSTRUCTIONS: ${{ inputs.additional_instructions }}
63+
INPUT_ASSESSMENT_CRITERIA: ${{ inputs.assessment_criteria }}
64+
INPUT_MODEL: ${{ inputs.model }}
65+
INPUT_BLOCKED_PATHS: ${{ inputs.blocked_paths }}
3.34 MB
Binary file not shown.

autosolve/build.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Cross-compile the autosolve binary for linux/amd64 (GitHub Actions runners).
5+
# The resulting binary is committed to the repo so the action doesn't need
6+
# Go installed at runtime.
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
cd "$SCRIPT_DIR"
10+
11+
OUTDIR="bin"
12+
mkdir -p "$OUTDIR"
13+
14+
echo "Building autosolve for linux/amd64..."
15+
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o "$OUTDIR/autosolve-linux-amd64" ./cmd/autosolve
16+
17+
echo "Built $OUTDIR/autosolve-linux-amd64"
18+
ls -lh "$OUTDIR/autosolve-linux-amd64"

autosolve/cmd/autosolve/main.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/signal"
8+
9+
"github.com/cockroachdb/actions/autosolve/internal/action"
10+
"github.com/cockroachdb/actions/autosolve/internal/assess"
11+
"github.com/cockroachdb/actions/autosolve/internal/claude"
12+
"github.com/cockroachdb/actions/autosolve/internal/config"
13+
"github.com/cockroachdb/actions/autosolve/internal/git"
14+
"github.com/cockroachdb/actions/autosolve/internal/github"
15+
"github.com/cockroachdb/actions/autosolve/internal/implement"
16+
)
17+
18+
const usage = `Usage: autosolve <command>
19+
20+
Commands:
21+
assess Run assessment phase
22+
implement Run implementation phase
23+
`
24+
25+
func main() {
26+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
27+
defer cancel()
28+
29+
if len(os.Args) < 2 {
30+
fatalf(usage)
31+
}
32+
33+
var err error
34+
switch os.Args[1] {
35+
case "assess":
36+
err = runAssess(ctx)
37+
case "implement":
38+
err = runImplement(ctx)
39+
default:
40+
fatalf("unknown command: %s\n\n%s", os.Args[1], usage)
41+
}
42+
43+
if err != nil {
44+
action.LogError(err.Error())
45+
os.Exit(1)
46+
}
47+
}
48+
49+
func fatalf(format string, args ...any) {
50+
fmt.Fprintf(os.Stderr, format+"\n", args...)
51+
os.Exit(1)
52+
}
53+
54+
func runAssess(ctx context.Context) error {
55+
cfg, err := config.LoadAssessConfig()
56+
if err != nil {
57+
return err
58+
}
59+
if err := config.ValidateAuth(); err != nil {
60+
return err
61+
}
62+
tmpDir, err := ensureTmpDir()
63+
if err != nil {
64+
return err
65+
}
66+
return assess.Run(ctx, cfg, &claude.CLIRunner{}, tmpDir)
67+
}
68+
69+
func runImplement(ctx context.Context) error {
70+
cfg, err := config.LoadImplementConfig()
71+
if err != nil {
72+
return err
73+
}
74+
if err := config.ValidateAuth(); err != nil {
75+
return err
76+
}
77+
tmpDir, err := ensureTmpDir()
78+
if err != nil {
79+
return err
80+
}
81+
82+
gitClient := &git.CLIClient{}
83+
defer implement.Cleanup(gitClient)
84+
85+
ghClient := &github.GithubClient{Token: cfg.PRCreateToken}
86+
return implement.Run(ctx, cfg, &claude.CLIRunner{}, ghClient, gitClient, tmpDir)
87+
}
88+
89+
func ensureTmpDir() (string, error) {
90+
dir := os.Getenv("AUTOSOLVE_TMPDIR")
91+
if dir != "" {
92+
return dir, nil
93+
}
94+
dir, err := os.MkdirTemp("", "autosolve_*")
95+
if err != nil {
96+
return "", fmt.Errorf("creating temp dir: %w", err)
97+
}
98+
os.Setenv("AUTOSOLVE_TMPDIR", dir)
99+
return dir, nil
100+
}

autosolve/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/cockroachdb/actions/autosolve
2+
3+
go 1.23.8

autosolve/go.sum

Whitespace-only changes.

0 commit comments

Comments
 (0)