Skip to content

Feat self healing prs #1

Feat self healing prs

Feat self healing prs #1

name: Claude Code Improvements
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Shared with claude-watcher: both mutate the PR branch (commit + push), so they
# must not run simultaneously. Keying on head branch serializes reviewer and
# watcher runs targeting the same PR into a single queue.
concurrency:
group: claude-pr-${{ github.event.pull_request.head.ref }}
cancel-in-progress: false
jobs:
claude-review:
runs-on: self-hosted
if: github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: write
pull-requests: write
issues: read
id-token: write
steps:
- name: Checkout PR branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Compute PR diff stats
id: diff
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git fetch --no-tags origin "+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF"
merge_base=$(git merge-base "origin/$BASE_REF" "$HEAD_SHA")
changed_files=$(git diff --name-only "$merge_base..$HEAD_SHA" | wc -l | tr -d ' ')
changed_lines=$(git diff --shortstat "$merge_base..$HEAD_SHA" | awk '{ ins=0; del=0; for (i=1;i<=NF;i++) { if ($i ~ /insertion/) ins=$(i-1); if ($i ~ /deletion/) del=$(i-1) } print ins + del }')
changed_lines=${changed_lines:-0}
{
echo "files=$changed_files"
echo "lines=$changed_lines"
echo 'file_list<<__EOF__'
git diff --name-only "$merge_base..$HEAD_SHA" | head -50
echo '__EOF__'
} >> "$GITHUB_OUTPUT"
- name: Classify PR complexity with Haiku
id: classify
uses: ./.github/actions/classify-complexity
with:
oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
Classify this pull request's review complexity. Reply with EXACTLY one lowercase word and nothing else: 'simple' or 'complex'.
simple = typo fix, docs tweak, one-file obvious bug, rename, trivial refactor within a single function, small test-only change
complex = multi-file change, new feature, architecture or API change, deep debugging, performance work, security-sensitive code, anything with unclear scope or touching more than ~3 files
TITLE: ${{ github.event.pull_request.title }}
STATS: ${{ steps.diff.outputs.files }} files, ${{ steps.diff.outputs.lines }} lines changed
FILES:
${{ steps.diff.outputs.file_list }}
DESCRIPTION:
${{ github.event.pull_request.body }}
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: |
https://github.com/anthropics/claude-code.git
https://github.com/abnegate/claudes.git
plugins: |
code-review@claude-code-plugins
skills@claudes
use_sticky_comment: 'false'
use_commit_signing: 'true'
show_full_output: 'true'
claude_args: |
--model ${{ steps.classify.outputs.model }}
--fallback-model ${{ steps.classify.outputs.fallback }}
--dangerously-skip-permissions
prompt: |
You are reviewing and fixing PR #$PR_NUMBER in $REPO.
## Coordination with the CI watcher
The `claude-watcher` workflow also pushes to this PR branch when CI fails.
We share a concurrency group (branch-keyed), so only one of us runs at a time —
but the OTHER may have pushed between the last event and this job starting.
Before committing in STEP 5, `git fetch origin` and `git pull --rebase origin <branch>`.
If rebase conflicts, resolve them (prefer the other side's changes unless they
contradict a finding you're fixing), then continue.
## STEP 1 — Analyze
Run `/code-review:code-review` for all CRITICAL/HIGH/MEDIUM findings. Skip low/nits.
## STEP 2 — Post inline review
Build /tmp/review.json with this structure and post it:
```json
{
"event": "COMMENT",
"body": "## Code Review\n\n**N finding(s)**\n\nSee inline comments. Fixes incoming.",
"comments": [
{"path": "file.php", "line": 42, "body": "**[SEVERITY]** ...\n\nExplanation + fix."}
]
}
```
Post: `gh api repos/$REPO/pulls/$PR_NUMBER/reviews --input /tmp/review.json`
If zero findings: post "No critical/high/medium findings." and STOP.
## STEP 3 — Fix in parallel via isolated worktree subagents
For MAXIMUM speed, launch one Agent per finding using worktree isolation.
Findings in different files run in TRUE parallel — launch them ALL in one message.
Findings in the SAME file go to the SAME agent to avoid conflicts.
Each agent prompt must be self-contained:
- Include the finding: severity, file path, line numbers, what's wrong, how to fix
- Tell it to verify the fix compiles (read CLAUDE.md for the build command)
- Tell it NOT to touch other files or make unrelated changes
Example — 3 findings in 3 files, all launched at once:
Agent({description: "Fix 1", isolation: "worktree", prompt: "Fix [HIGH] ... in file.php line 42 ..."})
Agent({description: "Fix 2", isolation: "worktree", prompt: "Fix [MEDIUM] ... in other.php line 99 ..."})
Agent({description: "Fix 3", isolation: "worktree", prompt: "Fix [MEDIUM] ... in third.php line 7 ..."})
## STEP 4 — Consolidate
After all agents finish, apply their changes to the main checkout:
- Each worktree agent returns the files it changed
- Cherry-pick or manually apply each agent's diff to the working tree
- If two agents touched the same file, merge carefully
- Verify final result compiles
## STEP 5 — Commit and push
- `git fetch origin && git pull --rebase origin $HEAD_BRANCH` to absorb any
commits the watcher (or the PR author) pushed while this job was queued
- Capture the pre-commit tip: `BEFORE_SHA=$(git rev-parse HEAD)`
- Stage only fix files
- Commit: `(fix): address review findings — X HIGH, Y MEDIUM`
- Body: one bullet per finding
- Push to PR branch
- Capture the post-push tip: `AFTER_SHA=$(git rev-parse HEAD)`
## STEP 6 — Post summary comment with compare link
After a successful push, add a follow-up comment linking to a compare view
of everything this run added, so reviewers can see exactly what changed:
COMPARE_URL="https://github.com/$REPO/compare/$BEFORE_SHA...$AFTER_SHA"
gh pr comment $PR_NUMBER --repo $REPO --body "Fixes pushed: $COMPARE_URL
<bulleted list of commits: \`sha\` — subject>"
If BEFORE_SHA equals AFTER_SHA (nothing was actually pushed — e.g. all
fixes were no-ops after rebase), skip this step.
Rules:
- Do NOT skip findings.
- Maximize parallelism — launch as many worktree agents as there are independent file groups.
- Each agent prompt must be fully self-contained (it has no context from this conversation).
- Never push to main.