Skip to content

Commit 6fd24ba

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 2d1d405 commit 6fd24ba

30 files changed

Lines changed: 3704 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 test ./... -count=1
18+
- name: Check precompiled binary is up to date
19+
run: |
20+
binary_version="$(./autosolve/bin/autosolve-linux-amd64 version)"
21+
source_hash="$(git ls-tree -r HEAD autosolve/ | grep --invert-match 'autosolve/bin/' | awk '{print $3}' | sort | git hash-object --stdin)"
22+
expected_suffix="+${source_hash:0:8}"
23+
if [[ "$binary_version" != *"$expected_suffix" ]]; then
24+
echo "::error::Precompiled binary is stale (binary: $binary_version, source hash: ${source_hash:0:8}). Run ./autosolve/build.sh and commit the result."
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 }}
2.21 MB
Binary file not shown.

autosolve/build.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
# Compute a hash of the source tree (excluding bin/) so CI can verify the
15+
# committed binary matches the source without a bit-for-bit comparison.
16+
# Run git ls-tree from the repo root so path prefixes are consistent.
17+
SOURCE_HASH="$(cd "$(git rev-parse --show-toplevel)" && git ls-tree -r HEAD autosolve/ | grep --invert-match 'autosolve/bin/' | awk '{print $3}' | sort | git hash-object --stdin)"
18+
VERSION="$(git describe --tags --always)+${SOURCE_HASH:0:8}"
19+
20+
echo "Building autosolve for linux/amd64 ($VERSION)..."
21+
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath \
22+
-ldflags="-s -w -X main.BuildSHA=$VERSION" \
23+
-o "$OUTDIR/autosolve-linux-amd64" ./cmd/autosolve
24+
25+
echo "Built $OUTDIR/autosolve-linux-amd64"
26+
ls -lh "$OUTDIR/autosolve-linux-amd64"

autosolve/cmd/autosolve/main.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
// BuildSHA is set at build time via -ldflags.
19+
var BuildSHA = "dev"
20+
21+
const usage = `Usage: autosolve <command>
22+
23+
Commands:
24+
assess Run assessment phase
25+
implement Run implementation phase
26+
version Print the git SHA this binary was built from
27+
`
28+
29+
func main() {
30+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
31+
defer cancel()
32+
33+
if len(os.Args) < 2 {
34+
fatalf(usage)
35+
}
36+
37+
var err error
38+
switch os.Args[1] {
39+
case "assess":
40+
err = runAssess(ctx)
41+
case "implement":
42+
err = runImplement(ctx)
43+
case "version":
44+
fmt.Println(BuildSHA)
45+
return
46+
default:
47+
fatalf("unknown command: %s\n\n%s", os.Args[1], usage)
48+
}
49+
50+
if err != nil {
51+
action.LogError(err.Error())
52+
os.Exit(1)
53+
}
54+
}
55+
56+
func fatalf(format string, args ...any) {
57+
fmt.Fprintf(os.Stderr, format+"\n", args...)
58+
os.Exit(1)
59+
}
60+
61+
func runAssess(ctx context.Context) error {
62+
cfg, err := config.LoadAssessConfig()
63+
if err != nil {
64+
return err
65+
}
66+
if err := config.ValidateAuth(); err != nil {
67+
return err
68+
}
69+
tmpDir, err := ensureTmpDir()
70+
if err != nil {
71+
return err
72+
}
73+
return assess.Run(ctx, cfg, &claude.CLIRunner{}, tmpDir)
74+
}
75+
76+
func runImplement(ctx context.Context) error {
77+
cfg, err := config.LoadImplementConfig()
78+
if err != nil {
79+
return err
80+
}
81+
if err := config.ValidateAuth(); err != nil {
82+
return err
83+
}
84+
tmpDir, err := ensureTmpDir()
85+
if err != nil {
86+
return err
87+
}
88+
89+
gitClient := &git.CLIClient{}
90+
defer implement.Cleanup(gitClient)
91+
92+
ghClient := &github.GithubClient{Token: cfg.PRCreateToken}
93+
return implement.Run(ctx, cfg, &claude.CLIRunner{}, ghClient, gitClient, tmpDir)
94+
}
95+
96+
func ensureTmpDir() (string, error) {
97+
dir := os.Getenv("AUTOSOLVE_TMPDIR")
98+
if dir != "" {
99+
return dir, nil
100+
}
101+
dir, err := os.MkdirTemp("", "autosolve_*")
102+
if err != nil {
103+
return "", fmt.Errorf("creating temp dir: %w", err)
104+
}
105+
os.Setenv("AUTOSOLVE_TMPDIR", dir)
106+
return dir, nil
107+
}

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)