Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2026-04-28 16:07:35 +02:00
commit 81207143f3
5 changed files with 329 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
### macOS
# Finder metadata
.DS_Store
# Thumbnails
._*
# Custom folder icons
Icon
# Volume root files
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
+102
View File
@@ -0,0 +1,102 @@
# claude-review
A Gitea composite action that reviews pull requests using Claude AI. It reads
review instructions from a prompt file in your target branch, sends the PR diff
to the Claude API, and posts the result as a PR comment.
## Usage
Create `.gitea/workflows/claude-review.yml` in any repository you want to
enable reviews for:
```yaml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: mars3142/claude-review@v1
with:
api_key: ${{ secrets.CLAUDE_API_KEY }}
gitea_token: ${{ secrets.GITEA_TOKEN }}
```
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `api_key` | yes | — | Anthropic API key |
| `gitea_token` | yes | — | Gitea token for posting comments (`GITEA_TOKEN` is set automatically by Gitea Actions) |
| `prompt_file` | no | `.claude/review-prompt.md` | Path to the review instructions file, resolved from the target branch |
| `model` | no | `claude-sonnet-4-6` | Claude model ID to use |
## Setup
### 1. Add the API key secret
In the target repository go to **Settings → Secrets** and add:
- `CLAUDE_API_KEY` — your [Anthropic API key](https://console.anthropic.com/)
`GITEA_TOKEN` is injected automatically by Gitea Actions and requires no manual setup.
### 2. Add the prompt file to your target branch
The action reads the review instructions from a file in your **target branch**
(e.g. `main`). This ensures that only instructions committed to the main line
are used, regardless of what a PR author puts in their branch.
Copy the example and adjust it to your needs:
```bash
cp review-prompt.example.md .claude/review-prompt.md
git add .claude/review-prompt.md
git commit -m "Add Claude review prompt"
git push
```
If you prefer a different path, pass it via the `prompt_file` input:
```yaml
- uses: mars3142/claude-review@v1
with:
api_key: ${{ secrets.CLAUDE_API_KEY }}
gitea_token: ${{ secrets.GITEA_TOKEN }}
prompt_file: docs/review-instructions.md
```
### 3. Open a pull request
The action triggers automatically on every PR that is opened, updated, or
reopened. It will post a comment with the review once it completes.
## How it works
1. **Fetch branches** — both the target branch and the PR head are fetched,
with a fallback to `refs/pull/<number>/head` for cross-fork PRs.
2. **Read prompt** — the instructions file is read directly from the target
branch via `git show`, so the caller's working tree is not affected.
3. **Generate diff** — a three-dot diff (`base...head`) is computed, capturing
only the changes introduced by the PR. Diffs larger than 80 KB are
truncated with a notice.
4. **Call Claude** — the prompt and diff are sent to the Claude API as a single
user message.
5. **Post comment** — the response is posted to the PR via the Gitea REST API.
## Prompt file
The prompt file is plain text or Markdown. It becomes the user message sent to
Claude, with the diff appended at the end. See
[`review-prompt.example.md`](review-prompt.example.md) for a ready-to-use
starting point.
## Requirements
- Gitea with Actions enabled
- An Anthropic API key with access to the chosen model
- `jq` and `curl` available on the runner (both are present on `ubuntu-latest`)
+153
View File
@@ -0,0 +1,153 @@
name: Claude Code Review
description: >
Reviews pull requests using Claude AI. Reads review instructions from a
prompt file in the target branch and posts the result as a PR comment.
author: mars3142
inputs:
api_key:
description: Anthropic API key
required: true
gitea_token:
description: Gitea token for posting PR comments
required: true
prompt_file:
description: Path to the review prompt file inside the target branch
required: false
default: .claude/review-prompt.md
model:
description: Claude model ID
required: false
default: claude-sonnet-4-6
runs:
using: composite
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch PR branches
shell: bash
run: |
git fetch origin "${{ github.base_ref }}"
git fetch origin "${{ github.head_ref }}" 2>/dev/null || \
git fetch origin \
"refs/pull/${{ github.event.pull_request.number }}/head:pr/${{ github.event.pull_request.number }}"
- name: Read prompt file from target branch
shell: bash
run: |
PROMPT_FILE="${{ inputs.prompt_file }}"
if ! git show "origin/${{ github.base_ref }}:${PROMPT_FILE}" \
> /tmp/claude-review-prompt.txt 2>/dev/null; then
echo "::error::Prompt file '${PROMPT_FILE}' not found in branch '${{ github.base_ref }}'"
exit 1
fi
echo "Prompt loaded from '${PROMPT_FILE}' ($(wc -c < /tmp/claude-review-prompt.txt) bytes)"
- name: Generate PR diff
shell: bash
run: |
HEAD_REF="${{ github.head_ref }}"
BASE_REF="origin/${{ github.base_ref }}"
if git diff "${BASE_REF}...origin/${HEAD_REF}" \
> /tmp/claude-review-diff.txt 2>/dev/null; then
true
else
git diff "${BASE_REF}" \
"pr/${{ github.event.pull_request.number }}" \
> /tmp/claude-review-diff.txt
fi
DIFF_SIZE=$(wc -c < /tmp/claude-review-diff.txt)
if [ "$DIFF_SIZE" -eq 0 ]; then
echo "::notice::Empty diff — skipping review"
echo "CLAUDE_SKIP=true" >> "$GITHUB_ENV"
exit 0
fi
# Truncate at 80 KB to stay within API limits
if [ "$DIFF_SIZE" -gt 81920 ]; then
head -c 81920 /tmp/claude-review-diff.txt > /tmp/claude-review-diff-trunc.txt
printf '\n\n[... diff truncated at 80 KB ...]\n' >> /tmp/claude-review-diff-trunc.txt
mv /tmp/claude-review-diff-trunc.txt /tmp/claude-review-diff.txt
echo "::warning::Diff truncated to 80 KB"
fi
echo "Diff ready ($(wc -c < /tmp/claude-review-diff.txt) bytes)"
- name: Call Claude API
if: env.CLAUDE_SKIP != 'true'
shell: bash
env:
CLAUDE_API_KEY: ${{ inputs.api_key }}
run: |
jq -n \
--arg model "${{ inputs.model }}" \
--arg prompt "$(cat /tmp/claude-review-prompt.txt)" \
--arg diff "$(cat /tmp/claude-review-diff.txt)" \
'{
model: $model,
max_tokens: 4096,
messages: [{
role: "user",
content: ($prompt + "\n\n---\n\n## Pull Request Diff\n\n```diff\n" + $diff + "\n```")
}]
}' > /tmp/claude-review-request.json
HTTP_STATUS=$(curl -s -w "%{http_code}" -o /tmp/claude-review-response.json \
-X POST "https://api.anthropic.com/v1/messages" \
-H "x-api-key: ${CLAUDE_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d @/tmp/claude-review-request.json)
if [ "$HTTP_STATUS" -ne 200 ]; then
echo "::error::Claude API returned HTTP ${HTTP_STATUS}"
jq -r '.error.message // .' /tmp/claude-review-response.json
exit 1
fi
REVIEW=$(jq -r '.content[0].text' /tmp/claude-review-response.json)
if [ -z "$REVIEW" ] || [ "$REVIEW" = "null" ]; then
echo "::error::Empty response from Claude API"
cat /tmp/claude-review-response.json
exit 1
fi
printf '%s' "$REVIEW" > /tmp/claude-review-result.txt
echo "Review ready ($(wc -c < /tmp/claude-review-result.txt) bytes)"
- name: Post review comment
if: env.CLAUDE_SKIP != 'true'
shell: bash
env:
GITEA_TOKEN: ${{ inputs.gitea_token }}
run: |
jq -n \
--arg review "$(cat /tmp/claude-review-result.txt)" \
--arg model "${{ inputs.model }}" \
'{body: ("## Claude Code Review\n\n" + $review + "\n\n---\n*Review generated by `" + $model + "`*")}' \
> /tmp/claude-review-comment.json
HTTP_STATUS=$(curl -s -w "%{http_code}" -o /tmp/claude-review-comment-response.json \
-X POST \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d @/tmp/claude-review-comment.json)
if [ "$HTTP_STATUS" -ne 201 ]; then
echo "::error::Failed to post comment (HTTP ${HTTP_STATUS})"
cat /tmp/claude-review-comment-response.json
exit 1
fi
echo "Review posted to PR #${{ github.event.pull_request.number }}"
+22
View File
@@ -0,0 +1,22 @@
# Place this file in your repo under .gitea/workflows/claude-review.yml
#
# Required secrets: CLAUDE_API_KEY (Anthropic API key)
# GITEA_TOKEN (set automatically by Gitea)
# Optional variable: CLAUDE_PROMPT_FILE (default: .claude/review-prompt.md)
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: mars3142/claude-review@v1
with:
api_key: ${{ secrets.CLAUDE_API_KEY }}
gitea_token: ${{ secrets.GITEA_TOKEN }}
# prompt_file: .claude/review-prompt.md # optional, this is the default
# model: claude-sonnet-4-6 # optional, this is the default
+34
View File
@@ -0,0 +1,34 @@
# Claude Code Review Instructions
You are a thorough and constructive code reviewer. Analyze the pull request diff below and provide a structured review.
## Focus areas
1. **Correctness** — logic errors, off-by-one, unhandled edge cases, wrong assumptions
2. **Security** — injection vulnerabilities, improper auth/authz, sensitive data exposure, insecure defaults
3. **Code quality** — readability, naming, duplication, overly complex logic
4. **Error handling** — missing error checks, swallowed exceptions, unhelpful error messages
5. **Tests** — missing tests for new behaviour, tests that don't actually verify the change
6. **Performance** — obvious inefficiencies, unnecessary allocations or queries, N+1 problems
## Response format
### Summary
One short paragraph describing what the PR does.
### Issues
List each issue with:
- **Severity**: `critical` | `major` | `minor`
- **File & location** (if determinable from the diff)
- **Description** and suggested fix
If no issues are found, state that explicitly.
### Suggestions
Non-blocking improvements worth considering (refactoring, better naming, etc.).
### Verdict
End with exactly one of:
-**Approve**
- ⚠️ **Approve with suggestions**
-**Request changes**