diff --git a/.gitea/workflows/claude.yml b/.gitea/workflows/claude.yml index bc4b94f..bfa5dcc 100644 --- a/.gitea/workflows/claude.yml +++ b/.gitea/workflows/claude.yml @@ -14,6 +14,7 @@ jobs: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 @@ -23,21 +24,39 @@ jobs: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} run: | - # Configure git for Claude to use + set +e + + # Configure git git config user.name "Claude Bot" git config user.email "claude@localhost" git remote set-url origin "http://admin:${GIT_TOKEN}@localhost:3000/${{ github.repository }}.git" - # Extract issue info ISSUE_NUMBER="${{ github.event.issue.number }}" ISSUE_TITLE="${{ github.event.issue.title }}" 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}" \ -H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))") - # Get comment body if triggered by comment COMMENT_BODY="" if [ "${{ github.event_name }}" = "issue_comment" ]; then COMMENT_ID="${{ github.event.comment.id }}" @@ -45,17 +64,10 @@ jobs: -H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))") fi - # Create working branch BRANCH="claude/issue-${ISSUE_NUMBER}" git checkout -b "${BRANCH}" - # Post status 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\": \"Claude is working on this issue...\"}" - - # Run Claude Code - it handles everything: code changes, commits, push, and PR creation + # Run Claude Code with cost controls claude -p "You are working on the repository ${REPO} (Gitea instance at http://localhost:3000). A Gitea issue needs your attention: @@ -63,19 +75,55 @@ jobs: Description: ${ISSUE_BODY} Additional context: ${COMMENT_BODY} - Instructions: - 1. Read and understand the codebase + 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. + + Steps: + 1. Read and understand the relevant parts of the codebase 2. Implement the requested changes 3. Commit your changes with a descriptive message 4. Push branch ${BRANCH} to origin 5. Create a pull request targeting main that references issue #${ISSUE_NUMBER} - 6. Comment on issue #${ISSUE_NUMBER} with a link to the PR + 6. Post a comment on issue #${ISSUE_NUMBER} summarizing what you did 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. + 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" \ - --mcp-config /home/claude-runner/.claude/mcp-gitea.json \ - --max-turns 50 \ + --model "${CLAUDE_MODEL}" \ + --max-turns "${MAX_TURNS}" \ + --max-budget-usd "${MAX_BUDGET}" \ + --effort "${EFFORT}" \ --permission-mode bypassPermissions \ - --output-format text 2>&1 | tee /home/claude-runner/last-claude-output.txt + --output-format json 2>&1 > /tmp/claude-result.json + + CLAUDE_EXIT=$? + + # Extract cost from JSON output + 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") + + # Amend the last commit to include cost and model + 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} + + Claude model: ${CLAUDE_MODEL} | API cost: ${COST}" --no-verify + git push origin "${BRANCH}" --force + 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}