name: Claude Code Assistant on: issues: types: [opened, labeled] issue_comment: types: [created] concurrency: group: claude-${{ github.event.issue.number }} cancel-in-progress: true jobs: claude-code: if: >- (github.event_name == 'issues' && contains(toJSON(github.event.issue.labels), 'claude')) || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.login != 'admin') runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Run Claude on Issue env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} run: | 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" 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 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',''))") COMMENT_BODY="" if [ "${{ github.event_name }}" = "issue_comment" ]; then COMMENT_ID="${{ github.event.comment.id }}" COMMENT_BODY=$(curl -s "http://localhost:3000/api/v1/repos/${REPO}/issues/comments/${COMMENT_ID}" \ -H "Authorization: token ${GIT_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))") fi BRANCH="claude/issue-${ISSUE_NUMBER}" git checkout -b "${BRANCH}" # 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: Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE} Description: ${ISSUE_BODY} Additional context: ${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. 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. 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 and comments. Gitea API token is available as env var GIT_TOKEN." \ --allowedTools "Bash,Read,Edit,Write,Glob,Grep" \ --model "${CLAUDE_MODEL}" \ --max-turns "${MAX_TURNS}" \ --max-budget-usd "${MAX_BUDGET}" \ --effort "${EFFORT}" \ --permission-mode bypassPermissions \ --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}