name: Claude Coder
on:
issues:
types: [opened, labeled]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to process'
required: true
type: number
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
actions: write
jobs:
solve-issue:
runs-on: ubuntu-latest
if: |
github.actor == 'tuannvm' && (
(github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'claude')) ||
github.event_name == 'workflow_dispatch'
)
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get issue details
id: issue
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
ISSUE_NUMBER="${{ github.event.inputs.issue_number }}"
else
ISSUE_NUMBER="${{ github.event.issue.number }}"
fi
# Fetch issue details using GitHub API
ISSUE_DATA=$(gh api repos/${{ github.repository }}/issues/${ISSUE_NUMBER})
ISSUE_TITLE=$(echo "$ISSUE_DATA" | jq -r '.title')
ISSUE_BODY=$(echo "$ISSUE_DATA" | jq -r '.body')
ISSUE_LABELS=$(echo "$ISSUE_DATA" | jq -r '.labels[].name' | tr '\n' ',' | sed 's/,$//')
echo "number=${ISSUE_NUMBER}" >> $GITHUB_OUTPUT
echo "title=${ISSUE_TITLE}" >> $GITHUB_OUTPUT
echo "labels=${ISSUE_LABELS}" >> $GITHUB_OUTPUT
# Create issue context file
cat > issue_context.md << EOF
# GitHub Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}
## Labels: ${ISSUE_LABELS}
## Description:
${ISSUE_BODY}
EOF
echo "Issue context saved to issue_context.md"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create feature branch
id: branch
run: |
BRANCH_NAME="claude/issue-${{ steps.issue.outputs.number }}"
# Check if branch already exists
if git ls-remote --heads origin ${BRANCH_NAME} | grep -q ${BRANCH_NAME}; then
echo "Branch ${BRANCH_NAME} already exists, using timestamp suffix"
BRANCH_NAME="${BRANCH_NAME}-$(date +%s)"
fi
git checkout -b ${BRANCH_NAME}
echo "name=${BRANCH_NAME}" >> $GITHUB_OUTPUT
- name: Run Claude Code Action
id: claude
uses: grll/claude-code-action@beta
with:
use_oauth: true
model: "claude-sonnet-4-20250514"
claude_access_token: ${{ secrets.CLAUDE_ACCESS_TOKEN }}
claude_refresh_token: ${{ secrets.CLAUDE_REFRESH_TOKEN }}
claude_expires_at: ${{ secrets.CLAUDE_EXPIRES_AT }}
secrets_admin_pat: ${{ secrets.SECRETS_ADMIN_PAT }}
timeout_minutes: "30"
prompt: |
Please analyze and solve the GitHub issue described in issue_context.md.
Instructions:
1. Read and fully understand the issue
2. Implement the necessary changes to fix the issue
3. Follow the project's coding standards in CLAUDE.md
4. Run tests using make test if applicable
5. Run linting using make lint
6. Only make changes that directly address the issue
7. Do not create new documentation unless explicitly requested
The issue details are in issue_context.md
- name: Check for changes
id: changes
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
# Get list of changed files
CHANGED_FILES=$(git diff --name-only)
echo "Changed files:"
echo "$CHANGED_FILES"
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Run tests
if: steps.changes.outputs.has_changes == 'true'
continue-on-error: true
id: tests
run: |
if [ -f "Makefile" ] && grep -q "^test:" Makefile; then
echo "Running tests..."
if make test > test_output.log 2>&1; then
echo "test_passed=true" >> $GITHUB_OUTPUT
echo "✅ Tests passed"
else
echo "test_failed=true" >> $GITHUB_OUTPUT
echo "❌ Tests failed (see test_output.log)"
fi
else
echo "No test target found in Makefile"
echo "test_skipped=true" >> $GITHUB_OUTPUT
fi
- name: Commit and push changes
if: steps.changes.outputs.has_changes == 'true'
run: |
git add -A
git commit -m "fix: resolve issue #${{ steps.issue.outputs.number }}
${{ steps.issue.outputs.title }}
Automated fix generated by Claude Code
Co-authored-by: Claude <noreply@anthropic.com>"
git push origin ${{ steps.branch.outputs.name }}
- name: Create Pull Request
if: steps.changes.outputs.has_changes == 'true'
id: pr
run: |
PR_BODY="## Summary
This PR was automatically generated by Claude Code to address issue #${{ steps.issue.outputs.number }}.
### Issue
**Title:** ${{ steps.issue.outputs.title }}
**Number:** #${{ steps.issue.outputs.number }}
### Changes Made
\`\`\`
${{ steps.changes.outputs.changed_files }}
\`\`\`
### Testing
- [x] Code changes implemented by Claude Code
- ${{ steps.tests.outputs.test_passed == 'true' && '[x]' || '[ ]' }} Tests pass ${{ steps.tests.outputs.test_failed == 'true' && '❌' || '' }}
- [ ] Manual testing completed (please verify)
- [ ] Code review completed
### Notes
- This PR was automatically generated
- Please review all changes carefully
- Additional testing may be required
Fixes #${{ steps.issue.outputs.number }}
---
*🤖 Generated by Claude Code GitHub Action*"
PR_URL=$(gh pr create \
--title "fix: resolve issue #${{ steps.issue.outputs.number }} - ${{ steps.issue.outputs.title }}" \
--body "$PR_BODY" \
--base main \
--head ${{ steps.branch.outputs.name }} \
--label "automated-pr,claude-code" \
--assignee "${{ github.actor }}")
echo "url=${PR_URL}" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Add PR label to issue
if: steps.changes.outputs.has_changes == 'true' && steps.pr.outputs.url
run: |
# Add label to indicate PR was created
gh issue edit ${{ steps.issue.outputs.number }} --add-label "has-pr"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup on failure
if: failure() && steps.branch.outputs.name
continue-on-error: true
run: |
# Delete the branch if no changes were pushed
if ! git ls-remote --heads origin ${{ steps.branch.outputs.name }} | grep -q ${{ steps.branch.outputs.name }}; then
echo "No remote branch to clean up"
else
echo "Cleaning up remote branch ${{ steps.branch.outputs.name }}"
git push origin --delete ${{ steps.branch.outputs.name }} || true
fi
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v5
with:
name: claude-issue-solver-${{ steps.issue.outputs.number }}
path: |
issue_context.md
test_output.log
retention-days: 7