Compare commits

4 Commits

View File

@@ -6,53 +6,62 @@ on:
issue_comment: issue_comment:
types: [created] types: [created]
concurrency:
group: claude-${{ github.event.issue.number }}
cancel-in-progress: true
jobs: jobs:
claude-code: claude-code:
if: >- if: >-
(github.event_name == 'issues' && (github.event_name == 'issues' &&
contains(toJSON(github.event.issue.labels), 'claude')) || contains(toJSON(github.event.issue.labels), 'claude')) ||
(github.event_name == 'issue_comment' && (github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude')) contains(github.event.comment.body, '@claude') &&
github.event.comment.user.login != 'admin')
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run Claude on Issue - name: Run Claude on Issue
env: env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: | run: |
set -x set +e
# Only set ANTHROPIC_API_KEY if the secret is configured
if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
export ANTHROPIC_API_KEY="${{ secrets.ANTHROPIC_API_KEY }}"
fi
# Debug info
echo "Running as: $(whoami)"
echo "Home: $HOME"
echo "Claude version: $(claude --version 2>&1)"
echo "Working dir: $(pwd)"
ls -la
# Configure git # Configure git
git config user.name "Claude Bot" git config user.name "Claude Bot"
git config user.email "claude@localhost" git config user.email "claude@localhost"
git remote set-url origin "http://admin:${GIT_TOKEN}@localhost:3000/${{ github.repository }}.git" git remote set-url origin "http://admin:${GIT_TOKEN}@localhost:3000/${{ github.repository }}.git"
# Extract issue info
ISSUE_NUMBER="${{ github.event.issue.number }}" ISSUE_NUMBER="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}" ISSUE_TITLE="${{ github.event.issue.title }}"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
LABELS_JSON='${{ toJSON(github.event.issue.labels) }}'
# Determine model + cost limits from issue labels
# Default: haiku (cheap). Add claude:sonnet or claude:opus for harder tasks.
CLAUDE_MODEL="haiku"
MAX_TURNS=15
MAX_BUDGET="0.50"
EFFORT="low"
if echo "$LABELS_JSON" | grep -q '"claude:opus"'; then
CLAUDE_MODEL="claude-opus-4-6"
MAX_TURNS=40
MAX_BUDGET="5.00"
EFFORT="high"
elif echo "$LABELS_JSON" | grep -q '"claude:sonnet"'; then
CLAUDE_MODEL="claude-sonnet-4-6"
MAX_TURNS=25
MAX_BUDGET="2.00"
EFFORT="medium"
fi
# Fetch issue body via API
ISSUE_BODY=$(curl -s "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" \ ISSUE_BODY=$(curl -s "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" \
-H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))") -H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))")
echo "Issue: #${ISSUE_NUMBER} - ${ISSUE_TITLE}"
echo "Body: ${ISSUE_BODY}"
# Get comment body if triggered by comment
COMMENT_BODY="" COMMENT_BODY=""
if [ "${{ github.event_name }}" = "issue_comment" ]; then if [ "${{ github.event_name }}" = "issue_comment" ]; then
COMMENT_ID="${{ github.event.comment.id }}" COMMENT_ID="${{ github.event.comment.id }}"
@@ -60,79 +69,66 @@ jobs:
-H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))") -H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))")
fi fi
# Create working branch
BRANCH="claude/issue-${ISSUE_NUMBER}" BRANCH="claude/issue-${ISSUE_NUMBER}"
git checkout -b "${BRANCH}" git checkout -b "${BRANCH}"
# Post status comment # Run Claude Code with cost controls
curl -s -X POST "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \ claude -p "You are working on the repository ${REPO} (Gitea instance at http://localhost:3000).
-H "Authorization: token ${GIT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"Claude is working on this issue...\"}"
# Run Claude Code
CLAUDE_OUTPUT=$(claude -p "You are working on the repository ${REPO}.
A Gitea issue needs your attention: A Gitea issue needs your attention:
Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE} Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}
Description: ${ISSUE_BODY} Description: ${ISSUE_BODY}
Additional context: ${COMMENT_BODY}
Additional context from comment: ${COMMENT_BODY} IMPORTANT RULES:
- Do NOT retry failed commands more than once. If something fails twice, stop and report the error.
- Do NOT loop on failing tests. Fix the obvious issue or report it. Never run the same failing command 3+ times.
- If you cannot complete the task, push what you have, create the PR as draft, and explain what is blocked.
- Be efficient: read only files you need, make targeted edits, avoid unnecessary exploration.
Instructions: Steps:
1. Read and understand the codebase structure 1. Read and understand the relevant parts of the codebase
2. Implement the requested changes carefully 2. Implement the requested changes
3. Make sure the code is correct and complete 3. Commit your changes with a descriptive message
4. Commit all changes with a descriptive commit message 4. Push branch ${BRANCH} to origin
5. Create a pull request targeting main that references issue #${ISSUE_NUMBER}
6. Post a comment on issue #${ISSUE_NUMBER} summarizing what you did
You are on branch ${BRANCH}. Work in the current directory." \ Git is configured. You are on branch ${BRANCH}. Work in the current directory.
Use git commands to push, and curl to the Gitea API for PR creation and comments.
Gitea API token is available as env var GIT_TOKEN." \
--allowedTools "Bash,Read,Edit,Write,Glob,Grep" \ --allowedTools "Bash,Read,Edit,Write,Glob,Grep" \
--mcp-config /home/claude-runner/.claude/mcp-gitea.json \ --model "${CLAUDE_MODEL}" \
--max-turns 50 \ --max-turns "${MAX_TURNS}" \
--max-budget-usd "${MAX_BUDGET}" \
--effort "${EFFORT}" \
--permission-mode bypassPermissions \ --permission-mode bypassPermissions \
--output-format text 2>&1) || true --output-format json 2>&1 > /tmp/claude-result.json
echo "=== CLAUDE OUTPUT START ===" CLAUDE_EXIT=$?
echo "${CLAUDE_OUTPUT}"
echo "=== CLAUDE OUTPUT END ==="
# Also save to a persistent location for debugging # Extract cost from JSON output
echo "${CLAUDE_OUTPUT}" > /home/claude-runner/last-claude-output.txt COST=$(python3 -c "
import json
with open('/tmp/claude-result.json') as f:
data = json.load(f)
cost = data.get('total_cost_usd', 0)
print(f'\${cost:.4f}')
" 2>/dev/null || echo "unknown")
# Stage any remaining unstaged changes # Amend the last commit to include cost and model
git add -A if git log --oneline main..HEAD 2>/dev/null | head -1 | grep -q .; then
LAST_MSG=$(git log -1 --format=%B)
git commit --amend -m "${LAST_MSG}
echo "=== GIT STATUS ===" Claude model: ${CLAUDE_MODEL} | API cost: ${COST}" --no-verify
git status git push origin "${BRANCH}" --force
echo "=== GIT DIFF ==="
git diff --cached --stat
# Check if there are changes
if ! git diff --cached --quiet 2>/dev/null; then
git commit -m "Claude: Address issue #${ISSUE_NUMBER} - ${ISSUE_TITLE}"
git push origin "${BRANCH}"
# Create PR
PR_RESPONSE=$(curl -s -X POST "http://localhost:3000/api/v1/repos/${REPO}/pulls" \
-H "Authorization: token ${GIT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Claude: Fix #${ISSUE_NUMBER} - ${ISSUE_TITLE}\",
\"body\": \"Automated PR by Claude Code for issue #${ISSUE_NUMBER}\n\nCloses #${ISSUE_NUMBER}\",
\"head\": \"${BRANCH}\",
\"base\": \"main\"
}")
PR_URL=$(echo "${PR_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('html_url',''))" 2>/dev/null || echo "unknown")
curl -s -X POST "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-H "Authorization: token ${GIT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"Claude has created a pull request: ${PR_URL}\"}"
else
echo "No changes detected by Claude"
curl -s -X POST "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-H "Authorization: token ${GIT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"Claude reviewed the issue but made no code changes.\"}"
fi fi
# Post cost as comment
curl -s -X POST "http://localhost:3000/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-H "Authorization: token ${GIT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"Done (model: **${CLAUDE_MODEL}**, effort: ${EFFORT}, budget cap: \$${MAX_BUDGET}). API cost: **${COST}**\"}" > /dev/null
exit ${CLAUDE_EXIT}