Skip to content

Commit 9134765

Browse files
Add shared shell helpers, test framework, and shellcheck config
- Add .shellcheckrc with SC1091 global suppression - Add actions_helpers.sh with shared GitHub Actions helpers (log_error, log_warning, log_notice, set_output, etc.) - Add test_helpers.sh with run_test framework for bash tests - Add tests for actions_helpers and autotag-from-changelog - Update test.sh to discover *_test.sh files automatically - Update CLAUDE.md with project conventions Co-Authored-By: roachdev-claude <[email protected]>
1 parent 3094546 commit 9134765

10 files changed

Lines changed: 512 additions & 50 deletions

File tree

.shellcheckrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# All source calls use dynamic $SCRIPT_DIR paths that shellcheck cannot resolve.
2+
disable=SC1091
3+
# Allow following sourced files and resolve relative paths from the script's directory.
4+
external-sources=true
5+
source-path=SCRIPTDIR

CHANGELOG.md

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

1111
### Added
1212

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.
17+
- `github-issue-autosolve` reusable workflow: turnkey GitHub Issues
18+
integration with issue comments and label management.
19+
- `jira-autosolve` reusable workflow: turnkey Jira integration composing
20+
autosolve/assess + autosolve/implement with ticket comments and transitions.
1321
- `autotag-from-changelog` action: tag and push from CHANGELOG.md version
1422
change.

CLAUDE.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,48 @@ scripts that set up temporary git repos and validate behavior.
3535
syntax highlighting and testability.
3636
- update CHANGELOG.md with each change. If it's a breaking change, prefix
3737
with "Breaking Change: ". Try to keep change descriptions focused on user
38-
outcome.
38+
outcome. New entries go above older ones (the changelog grows upward).
3939
- CI runs on PRs: `test.yml` runs `./test.sh`, `actionlint.yml` lints workflow YAML files
40+
- Every shellcheck suppression (`disable`, `source`, etc.) must include a short
41+
comment explaining why the suppression is needed. SC1091 (can't follow
42+
sourced file) is disabled globally in `.shellcheckrc` since all sources use
43+
dynamic `$SCRIPT_DIR` paths.
44+
- In test files, prefer `cd "$(dirname "${BASH_SOURCE[0]}")"` at the top and then use literal
45+
relative paths for `source` (e.g., `source ../../actions_helpers.sh`). This
46+
enables IDE go-to-definition via `source-path=SCRIPTDIR` in `.shellcheckrc`
47+
without needing `# shellcheck source=` directives. In production scripts that
48+
cannot `cd`, use `SCRIPT_DIR` with `# shellcheck source=` directives for
49+
navigation.
50+
- `actions_helpers.sh` at the repo root provides shared helpers (`log_error`, `log_warning`,
51+
`log_notice`, `set_output`, `set_output_multiline`). Scripts source it via
52+
a relative path after `cd`-ing to their own directory.
53+
- Autosolve scripts (`assess.sh`, `implement.sh`, `github_issues.sh`, `shared.sh`) source their
54+
own dependencies via `BASH_SOURCE`-relative paths. No caller needs to source the
55+
chain — just source the script you need. Re-sourcing is idempotent.
56+
- In shell scripts, prefer long options over short flags for readability
57+
(e.g., `grep --quiet --fixed-strings` instead of `grep -qF`,
58+
`curl --silent --output /dev/null` instead of `curl -s -o /dev/null`).
59+
Exceptions: flags with no long form (e.g., `git checkout -b`) and
60+
universally understood short forms in test helpers (e.g., `rm -rf`).
61+
- Never discard stderr (e.g., `2>/dev/null`) in shell scripts or action
62+
steps. Suppressing stderr hides real errors and makes debugging harder.
63+
Using `2>&1` to merge stderr into stdout is acceptable in test helpers
64+
that need to capture all output for assertion, but avoid it in
65+
production scripts. Run each command on its own line so that `bash -e`
66+
(the default for GitHub Actions `run` steps) halts on failure and the
67+
return code is checked automatically.
68+
- In workflow YAML files, always use the latest major version of built-in
69+
GitHub Actions (e.g., `actions/checkout@v5`, `actions/upload-artifact@v4`).
70+
- In autosolve workflows, install the Claude CLI (npm) BEFORE any cloud
71+
authentication step, and move credential files out of the workspace
72+
immediately after authentication. npm post-install scripts run with the
73+
job's full environment, so installing after auth exposes credentials
74+
(e.g., the OIDC bearer token in `gha-creds-*.json`) to arbitrary code.
75+
The correct step order is: checkout → install CLI → authenticate →
76+
move credentials → run autosolve action.
77+
- Do not silently swallow errors. In shell scripts, avoid `|| return 0`,
78+
`|| true`, or `|| :` to suppress failures without logging — use
79+
`log_warning` to surface what went wrong. In Go code, avoid `return nil`
80+
on error paths without logging or returning the error. If ignoring an
81+
error is genuinely correct (e.g., best-effort cleanup), add a comment
82+
explaining why it's safe to ignore.

README.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,170 @@ permissions:
2929
contents: write
3030
```
3131

32+
### autosolve
33+
34+
Uses Claude Code to autonomously assess and implement solutions for tasks.
35+
Organized as two composite actions that can be used independently or together,
36+
plus reusable workflows for common integrations.
37+
38+
#### Actions
39+
40+
**`autosolve/assess`** — Runs Claude in read-only mode to evaluate whether a
41+
task is suitable for automated resolution.
42+
43+
```yaml
44+
- uses: cockroachdb/actions/autosolve/assess@v1
45+
id: assess
46+
with:
47+
prompt: "Fix the login bug described in issue #42"
48+
```
49+
50+
| Input | Default | Description |
51+
|---|---|---|
52+
| `prompt` | | Task description for Claude to assess |
53+
| `skill` | | Path to a skill/prompt file relative to the repo root |
54+
| `additional_instructions` | | Extra context appended after the task prompt |
55+
| `assessment_criteria` | *(built-in)* | Custom criteria for PROCEED/SKIP decision |
56+
| `model` | `claude-opus-4-6` | Claude model ID |
57+
| `blocked_paths` | `.github/workflows/` | Comma-separated path prefixes that cannot be modified |
58+
59+
| Output | Description |
60+
|---|---|
61+
| `assessment` | `PROCEED` or `SKIP` |
62+
| `summary` | Human-readable assessment reasoning |
63+
| `result` | Full Claude result text |
64+
65+
**`autosolve/implement`** — Runs Claude to implement a solution, validates
66+
changes against blocked paths, pushes to a fork, and creates a PR.
67+
68+
```yaml
69+
- uses: cockroachdb/actions/autosolve/implement@v1
70+
if: steps.assess.outputs.assessment == 'PROCEED'
71+
with:
72+
prompt: "Fix the login bug described in issue #42"
73+
fork_owner: my-bot
74+
fork_repo: my-repo
75+
fork_push_token: ${{ secrets.FORK_PUSH_TOKEN }}
76+
pr_create_token: ${{ secrets.PR_CREATE_TOKEN }}
77+
```
78+
79+
| Input | Default | Description |
80+
|---|---|---|
81+
| `prompt` | | Task description for Claude to implement |
82+
| `skill` | | Path to a skill/prompt file relative to the repo root |
83+
| `additional_instructions` | | Extra instructions appended after the task prompt |
84+
| `allowed_tools` | *(read/write/git tools)* | Claude `--allowedTools` string |
85+
| `model` | `claude-opus-4-6` | Claude model ID |
86+
| `max_retries` | `3` | Maximum implementation attempts |
87+
| `timeout_minutes` | `60` | Maximum wall-clock time |
88+
| `create_pr` | `true` | Whether to create a PR from the changes |
89+
| `pr_base_branch` | *(repo default)* | Base branch for the PR |
90+
| `pr_labels` | `autosolve` | Comma-separated labels to apply |
91+
| `pr_draft` | `true` | Whether to create as a draft PR |
92+
| `pr_title` | *(from commit)* | PR title |
93+
| `pr_body_template` | *(built-in)* | Template with `{{SUMMARY}}`, `{{STATS}}`, `{{BRANCH}}` placeholders |
94+
| `fork_owner` | | GitHub user/org that owns the fork |
95+
| `fork_repo` | | Fork repository name |
96+
| `fork_push_token` | | PAT with push access to the fork |
97+
| `pr_create_token` | | PAT with PR create access on upstream |
98+
| `blocked_paths` | `.github/workflows/` | Comma-separated blocked path prefixes |
99+
| `git_user_name` | `autosolve[bot]` | Git author/committer name |
100+
| `git_user_email` | `autosolve[bot]@users.noreply.github.com` | Git author/committer email |
101+
| `branch_suffix` | *(timestamp)* | Suffix for branch name (`autosolve/<suffix>`) |
102+
103+
| Output | Description |
104+
|---|---|
105+
| `status` | `SUCCESS` or `FAILED` |
106+
| `pr_url` | URL of the created PR |
107+
| `summary` | Human-readable summary |
108+
| `result` | Full Claude result text |
109+
| `branch_name` | Branch pushed to the fork |
110+
111+
#### Reusable Workflows
112+
113+
**Jira Autosolve** — Composes assess + implement with Jira comments and ticket
114+
transitions. Triggered via `workflow_call`.
115+
116+
```yaml
117+
jobs:
118+
solve:
119+
uses: cockroachdb/actions/.github/workflows/jira-autosolve.yml@v1
120+
with:
121+
ticket_id: PROJ-123
122+
title: ${{ needs.parse.outputs.title }}
123+
description: ${{ needs.parse.outputs.description }}
124+
jira_base_url: https://yourcompany.atlassian.net
125+
fork_owner: my-bot
126+
fork_repo: my-repo
127+
secrets:
128+
jira_token: ${{ secrets.JIRA_TOKEN }}
129+
fork_push_token: ${{ secrets.FORK_PUSH_TOKEN }}
130+
pr_create_token: ${{ secrets.PR_CREATE_TOKEN }}
131+
```
132+
133+
**GitHub Issue Autosolve** — Composes assess + implement with GitHub issue
134+
comments and label management. Triggered via `workflow_call`.
135+
136+
```yaml
137+
jobs:
138+
solve:
139+
uses: cockroachdb/actions/.github/workflows/github-issue-autosolve.yml@v1
140+
with:
141+
issue_number: ${{ github.event.issue.number }}
142+
issue_title: ${{ github.event.issue.title }}
143+
issue_body: ${{ github.event.issue.body }}
144+
fork_owner: my-bot
145+
fork_repo: my-repo
146+
secrets:
147+
fork_push_token: ${{ secrets.FORK_PUSH_TOKEN }}
148+
pr_create_token: ${{ secrets.PR_CREATE_TOKEN }}
149+
```
150+
151+
#### Authentication
152+
153+
**Reusable workflows** accept `auth_mode` as an input (`vertex`, `bedrock`, or
154+
omit for API key) and handle env var setup internally.
155+
156+
**Direct composite action usage** requires the caller to set up auth and pass
157+
the env vars on each action step:
158+
159+
```yaml
160+
# Example: Vertex AI auth for direct action usage
161+
- uses: google-github-actions/auth@v3
162+
with:
163+
project_id: my-project
164+
service_account: [email protected]
165+
workload_identity_provider: projects/.../providers/...
166+
167+
- uses: cockroachdb/actions/autosolve/assess@v1
168+
env:
169+
CLAUDE_CODE_USE_VERTEX: "1"
170+
ANTHROPIC_VERTEX_PROJECT_ID: my-project
171+
CLOUD_ML_REGION: us-east5
172+
with:
173+
prompt: "Fix the bug"
174+
```
175+
176+
Alternatively, set `ANTHROPIC_API_KEY` in the environment for direct API
177+
access, or configure Bedrock with `CLAUDE_CODE_USE_BEDROCK=1` and `AWS_REGION`.
178+
179+
#### Caller checkout
180+
181+
When using `workflow_dispatch`, `actions/checkout` defaults to the branch that
182+
triggered the workflow. This can include unrelated commits from that branch in
183+
the autosolve PR. Always check out the PR base branch explicitly:
184+
185+
```yaml
186+
- uses: actions/checkout@v5
187+
with:
188+
ref: main # checkout the PR base branch, not the trigger ref
189+
fetch-depth: 0
190+
persist-credentials: false # prevent checkout's credential helper from interfering with fork push
191+
```
192+
193+
The `issues: [labeled]` trigger doesn't have this problem since it always runs
194+
on the default branch.
195+
32196
## Development
33197

34198
Run all tests locally:

actions_helpers.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# Shared shell helpers for all GitHub Actions in this repo.
3+
4+
# GitHub Actions log commands — emit structured annotations via stdout.
5+
log_error() { echo "::error::$*"; }
6+
log_warning() { echo "::warning::$*"; }
7+
log_notice() { echo "::notice::$*"; }
8+
9+
# Write a single-line output: set_output key value
10+
set_output() {
11+
echo "$1=$2" >> "${GITHUB_OUTPUT:-/dev/null}"
12+
}
13+
14+
# Write a multiline output: set_output_multiline key value
15+
set_output_multiline() {
16+
local delim
17+
delim="GHEOF_$$_$(date +%s)"
18+
{
19+
echo "$1<<$delim"
20+
echo "$2"
21+
echo "$delim"
22+
} >> "${GITHUB_OUTPUT:-/dev/null}"
23+
}
24+
25+
# Verify a command is on PATH: require_command <name>
26+
require_command() {
27+
command -v "$1" >/dev/null || { log_error "$1 not found on PATH"; return 1; }
28+
}
29+
30+
# Truncate text to a maximum number of lines, appending a notice if truncated.
31+
# Usage: truncate_output <max_lines> <text>
32+
truncate_output() {
33+
local max_lines="$1"
34+
local text="$2"
35+
local line_count
36+
line_count="$(echo "$text" | wc -l | tr -d ' ')"
37+
if [ "$line_count" -gt "$max_lines" ]; then
38+
echo "$text" | head -"$max_lines"
39+
echo "[... truncated ($line_count lines total, showing first $max_lines)]"
40+
else
41+
echo "$text"
42+
fi
43+
}
44+
45+
# Append content to the GitHub Actions step summary.
46+
# Usage: write_step_summary <<EOF ... EOF
47+
write_step_summary() {
48+
cat >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
49+
}

0 commit comments

Comments
 (0)