claude-branch-automation.yml•13.6 kB
name: Claude Branch Automation
# Trigger when Claude pushes commits to its branches
# Using push event is more reliable than create event
on:
push:
branches:
- 'claude/**'
jobs:
complete-release-prep:
# SECURITY: Run automation ONLY for Claude-generated branches
# Prevents malicious collaborators from abusing this workflow
# Claude commits have actor: github-actions[bot] or triggering_actor: github-actions[bot]
# Branch pattern: claude/issue-{number}-{timestamp} or claude/{type}-{timestamp}
if: |
startsWith(github.ref_name, 'claude/') &&
(github.actor == 'github-actions[bot]' || github.triggering_actor == 'github-actions[bot]')
runs-on: ubuntu-latest
permissions:
contents: write # Need write to commit uv.lock
pull-requests: write # Need write to create PR
issues: write # Need write to comment on issues
steps:
- name: Security Verification
run: |
echo "=== Security Check ==="
echo "Branch: ${{ github.ref_name }}"
echo "Actor: ${{ github.actor }}"
echo "Triggering Actor: ${{ github.triggering_actor }}"
echo "Event Name: ${{ github.event_name }}"
# Verify this is a Claude-generated branch
if [[ "${{ github.actor }}" != "github-actions[bot]" ]] && [[ "${{ github.triggering_actor }}" != "github-actions[bot]" ]]; then
echo "ERROR: Workflow triggered by unauthorized user"
echo "Only Claude (github-actions[bot]) can trigger this workflow"
exit 1
fi
echo "✅ Security check passed - Claude-generated branch confirmed"
- name: Checkout branch
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
fetch-depth: 0 # Full history for proper git operations
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Run uv lock
id: uv_lock
run: |
# Run uv lock and capture if changes were made
uv lock
# Check if uv.lock was modified
if git diff --quiet uv.lock; then
echo "changed=false" >> $GITHUB_OUTPUT
echo "✅ uv.lock is already up to date"
else
echo "changed=true" >> $GITHUB_OUTPUT
echo "📝 uv.lock has been updated"
fi
- name: Commit uv.lock
if: steps.uv_lock.outputs.changed == 'true'
run: |
git add uv.lock
# Extract version from branch name or use generic message
VERSION=$(echo "${{ github.ref_name }}" | grep -oP 'v\d+\.\d+\.\d+' || echo "")
if [ -n "$VERSION" ]; then
git commit -m "chore: update uv.lock for $VERSION"
else
git commit -m "chore: update uv.lock"
fi
git push origin ${{ github.ref_name }}
- name: Run Quality Checks
id: quality_check
continue-on-error: true # Don't fail workflow, just capture result
run: |
echo "=== Running Quality Gate Checks ==="
# Get list of changed Python files compared to main
git diff origin/main...HEAD --name-only --diff-filter=ACMR > /tmp/changed_files.txt
echo "Changed files:"
cat /tmp/changed_files.txt
echo ""
# Run quality checks on changed files
# Note: GROQ_API_KEY or Gemini CLI required
# Exit codes: 0=pass, 1=warnings, 2=critical
if bash scripts/pr/run_quality_checks_on_files.sh --files-from /tmp/changed_files.txt; then
echo "quality_passed=true" >> $GITHUB_OUTPUT
echo "quality_status=passed" >> $GITHUB_OUTPUT
echo "blocking=false" >> $GITHUB_OUTPUT
exit 0
else
EXIT_CODE=$?
if [ $EXIT_CODE -eq 2 ]; then
echo "quality_passed=false" >> $GITHUB_OUTPUT
echo "quality_status=failed" >> $GITHUB_OUTPUT
echo "blocking=true" >> $GITHUB_OUTPUT
echo "::error::Quality gate FAILED - Security vulnerabilities detected"
exit 1 # Fail the step
else
echo "quality_passed=false" >> $GITHUB_OUTPUT
echo "quality_status=warnings" >> $GITHUB_OUTPUT
echo "blocking=false" >> $GITHUB_OUTPUT
echo "::warning::Quality gate has warnings but is non-blocking"
exit 0 # Don't fail the step
fi
fi
env:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
- name: Extract issue number from branch name
id: extract_issue
run: |
# Extract issue number from branch name like "claude/issue-254-20251129-2139"
ISSUE_NUM=$(echo "${{ github.ref_name }}" | grep -oP 'issue-\K\d+' || echo "")
echo "number=$ISSUE_NUM" >> $GITHUB_OUTPUT
echo "Found issue number: $ISSUE_NUM"
- name: Check if PR already exists
id: check_pr
env:
GH_TOKEN: ${{ github.token }}
run: |
# Check if PR from this branch already exists
PR_EXISTS=$(gh pr list --head "${{ github.ref_name }}" --json number --jq 'length')
echo "exists=$PR_EXISTS" >> $GITHUB_OUTPUT
if [ "$PR_EXISTS" -gt 0 ]; then
echo "⚠️ PR already exists for this branch"
else
echo "✅ No PR exists yet"
fi
- name: Read version from pyproject.toml
id: read_version
run: |
if [ ! -f "pyproject.toml" ]; then
echo "ERROR: pyproject.toml not found"
exit 1
fi
VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)
if [ -z "$VERSION" ]; then
echo "ERROR: Could not extract version from pyproject.toml"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION"
- name: Read CHANGELOG entry
id: changelog
run: |
if [ ! -f "CHANGELOG.md" ]; then
echo "WARNING: CHANGELOG.md not found, using generic entry"
echo "See commit history for details." > /tmp/changelog.md
else
# Extract the latest version entry from CHANGELOG.md
# Gets everything from first version header until the next one
awk '/^## \[[0-9]/ {p++} p==1' CHANGELOG.md > /tmp/changelog.md
if [ ! -s /tmp/changelog.md ]; then
echo "WARNING: Could not extract CHANGELOG entry, using generic"
echo "See CHANGELOG.md for details." > /tmp/changelog.md
fi
fi
echo "CHANGELOG entry to use:"
cat /tmp/changelog.md
- name: Create Pull Request
if: |
steps.check_pr.outputs.exists == '0' &&
steps.quality_check.outputs.blocking != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.read_version.outputs.version }}"
ISSUE_NUM="${{ steps.extract_issue.outputs.number }}"
BRANCH="${{ github.ref_name }}"
# Determine PR type from branch name
if echo "$BRANCH" | grep -q "fix"; then
PR_TYPE="fix"
elif echo "$BRANCH" | grep -q "feat"; then
PR_TYPE="feat"
else
PR_TYPE="chore"
fi
# Build PR title
if [ -n "$ISSUE_NUM" ]; then
PR_TITLE="${PR_TYPE}: release v${VERSION} (fixes #${ISSUE_NUM})"
else
PR_TITLE="${PR_TYPE}: release v${VERSION}"
fi
# Get quality check status for PR body
QUALITY_STATUS="${{ steps.quality_check.outputs.quality_status }}"
QUALITY_EMOJI="✅"
QUALITY_TEXT="All quality checks passed"
if [ "$QUALITY_STATUS" = "warnings" ]; then
QUALITY_EMOJI="⚠️"
QUALITY_TEXT="Quality checks passed with warnings (see workflow logs)"
elif [ "$QUALITY_STATUS" = "failed" ]; then
QUALITY_EMOJI="🔴"
QUALITY_TEXT="Quality checks failed (PR should not have been created)"
fi
# Build PR body
cat > /tmp/pr-body.md << EOF
## 🤖 Automated Release Preparation
This PR was automatically created by the Claude Branch Automation workflow.
## Changes
- Version bump to v${VERSION}
- Updated \`__init__.py\`, \`pyproject.toml\`, \`README.md\`
- Updated \`uv.lock\` (automated)
- Updated \`CHANGELOG.md\`
## Quality Checks
${QUALITY_EMOJI} **${QUALITY_TEXT}**
- Code complexity: Analyzed
- Security scan: Completed
- Status: \`${QUALITY_STATUS}\`
## CHANGELOG Entry
$(cat /tmp/changelog.md)
## Checklist
- [x] Version bumped in all required files
- [x] \`uv.lock\` updated (automated by workflow)
- [x] CHANGELOG.md updated
- [x] README.md updated
- [x] Quality checks completed
## Related
EOF
# Add issue reference if exists
if [ -n "$ISSUE_NUM" ]; then
echo "- Fixes #${ISSUE_NUM}" >> /tmp/pr-body.md
fi
echo "" >> /tmp/pr-body.md
echo "---" >> /tmp/pr-body.md
echo "🤖 Generated by Claude Branch Automation workflow" >> /tmp/pr-body.md
# Create PR
gh pr create \
--title "$PR_TITLE" \
--body-file /tmp/pr-body.md \
--base main \
--head "$BRANCH"
echo "✅ Pull Request created successfully"
- name: Comment on issue (Quality Check Blocked)
if: |
steps.extract_issue.outputs.number != '' &&
steps.quality_check.outputs.blocking == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
ISSUE_NUM="${{ steps.extract_issue.outputs.number }}"
VERSION="${{ steps.read_version.outputs.version }}"
cat > /tmp/quality-block-comment.md << EOF
## 🔴 Quality Gate Failed - PR Creation Blocked
The automated release preparation for **v${VERSION}** was blocked due to critical quality issues.
**Branch created:** \`${{ github.ref_name }}\`
**Critical Issues Detected:**
- Security vulnerabilities found in code changes
- PR creation was blocked to prevent merging vulnerable code
**Action Required:**
1. Review the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for detailed security scan results
2. Fix all security vulnerabilities in the branch
3. Push fixes to \`${{ github.ref_name }}\`
4. This workflow will re-run automatically and create the PR if checks pass
**Quality Gate Details:**
- Status: ${{ steps.quality_check.outputs.quality_status }}
- Blocking: ${{ steps.quality_check.outputs.blocking }}
🤖 Automated by Claude Branch Automation workflow with quality enforcement
EOF
gh issue comment "$ISSUE_NUM" --body-file /tmp/quality-block-comment.md
echo "✅ Quality block notification added to issue #${ISSUE_NUM}"
- name: Comment on issue
if: steps.extract_issue.outputs.number != '' && steps.check_pr.outputs.exists == '0' && steps.quality_check.outputs.blocking != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
ISSUE_NUM="${{ steps.extract_issue.outputs.number }}"
VERSION="${{ steps.read_version.outputs.version }}"
# Get PR number
PR_NUM=$(gh pr list --head "${{ github.ref_name }}" --json number --jq '.[0].number')
# Determine quality check status message
QUALITY_STATUS="${{ steps.quality_check.outputs.quality_status }}"
if [ "$QUALITY_STATUS" = "passed" ]; then
QUALITY_MSG="✅ Quality checks passed (complexity + security)"
elif [ "$QUALITY_STATUS" = "warnings" ]; then
QUALITY_MSG="⚠️ Quality checks passed with warnings (see PR for details)"
else
QUALITY_MSG="✅ Quality checks completed"
fi
cat > /tmp/issue-comment.md << EOF
## 🚀 Release Preparation Complete
I've automatically completed the release preparation for **v${VERSION}**:
✅ Branch created: \`${{ github.ref_name }}\`
✅ Version bumped in all files
✅ \`uv.lock\` updated automatically
${QUALITY_MSG}
✅ Pull Request created: #${PR_NUM}
**Next Steps:**
1. Review the PR: #${PR_NUM}
2. Once approved, merge to complete the release
3. Tag and GitHub release will follow standard workflow
🤖 Automated by Claude Branch Automation workflow with quality enforcement
EOF
gh issue comment "$ISSUE_NUM" --body-file /tmp/issue-comment.md
echo "✅ Comment added to issue #${ISSUE_NUM}"