Claude CI Auto-Fix #49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude CI Auto-Fix | |
| on: | |
| workflow_run: | |
| workflows: ["PR Checks", "Test Suite"] | |
| types: [completed] | |
| workflow_dispatch: | |
| inputs: | |
| task_type: | |
| description: 'Task type (ci-fix or sync-dev)' | |
| required: true | |
| default: 'ci-fix' | |
| type: choice | |
| options: | |
| - ci-fix | |
| - sync-dev | |
| target_branch: | |
| description: 'Target branch for sync-dev task' | |
| required: false | |
| default: 'dev' | |
| type: string | |
| source_branch: | |
| description: 'Source branch for sync-dev task' | |
| required: false | |
| default: 'main' | |
| type: string | |
| release_tag: | |
| description: 'Release tag that triggered sync' | |
| required: false | |
| type: string | |
| jobs: | |
| sync-dev: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.task_type == 'sync-dev' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Validate branch parameters | |
| run: | | |
| TARGET="${{ github.event.inputs.target_branch }}" | |
| SOURCE="${{ github.event.inputs.source_branch }}" | |
| if [[ "$TARGET" != "dev" ]]; then | |
| echo "::error::Security: Only 'dev' branch is allowed as target. Got: $TARGET" | |
| exit 1 | |
| fi | |
| if [[ "$SOURCE" != "main" ]]; then | |
| echo "::error::Security: Only 'main' branch is allowed as source. Got: $SOURCE" | |
| exit 1 | |
| fi | |
| echo "Branch validation passed: syncing $SOURCE -> $TARGET" | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.inputs.target_branch }} | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} | |
| - name: Run Claude Code for Dev Sync | |
| uses: anthropics/claude-code-action@v1 | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} | |
| prompt: | | |
| # Role: Git Branch Synchronization Assistant | |
| You are a Git branch synchronization assistant for repository ${{ github.repository }}. | |
| --- | |
| ## Context | |
| - **Source Branch**: ${{ github.event.inputs.source_branch }} (contains new release) | |
| - **Target Branch**: ${{ github.event.inputs.target_branch }} (needs to be synced) | |
| - **Release Tag**: ${{ github.event.inputs.release_tag }} | |
| --- | |
| ## Core Principles | |
| 1. **PRESERVE WORK**: Never lose commits from the target branch. | |
| 2. **SAFE OPERATIONS**: Use --force-with-lease, not --force. | |
| 3. **VERIFY BEFORE PUSH**: Always verify the result compiles and works. | |
| 4. **DOCUMENT CONFLICTS**: If conflicts cannot be resolved, document clearly. | |
| --- | |
| ## Execution Workflow | |
| ### Phase 1: Analysis | |
| ```bash | |
| git fetch origin | |
| git checkout ${{ github.event.inputs.target_branch }} | |
| echo "=== Current branch commits ===" | |
| git log --oneline -10 | |
| echo "=== Commits ahead of source ===" | |
| git rev-list --count origin/${{ github.event.inputs.source_branch }}..HEAD | |
| echo "=== Commits behind source ===" | |
| git rev-list --count HEAD..origin/${{ github.event.inputs.source_branch }} | |
| echo "=== Files that will conflict (preview) ===" | |
| git diff --name-only origin/${{ github.event.inputs.source_branch }} | |
| ``` | |
| ### Phase 2: Rebase Attempt | |
| ```bash | |
| git rebase origin/${{ github.event.inputs.source_branch }} | |
| ``` | |
| ### Phase 3: Conflict Resolution (If Needed) | |
| **Resolution priorities:** | |
| | File Type | Resolution Strategy | | |
| |-----------|---------------------| | |
| | `VERSION` / version files | Keep source (newer release version) | | |
| | `pyproject.toml` version | Keep source (newer release) | | |
| | `package.json` version | Keep source (newer release) | | |
| | `pnpm-lock.yaml` | Accept source, then regenerate | | |
| | Database migrations | Keep both, ensure ordering | | |
| | Source code | Merge intelligently, prefer target for features | | |
| | Config files | Merge both changes if possible | | |
| For each conflict: | |
| 1. Read the file with conflict markers | |
| 2. Understand what source branch changed vs target branch | |
| 3. Resolve and continue: `git add <file> && git rebase --continue` | |
| ### Phase 4: Verification | |
| ```bash | |
| # Backend verification | |
| if [ -d backend ]; then | |
| cd backend | |
| pip install -e . 2>/dev/null || true | |
| python -m compileall -q app | |
| python -c "from app.main import app; print('backend import: ok')" | |
| cd .. | |
| fi | |
| # Frontend verification | |
| if [ -d frontend ]; then | |
| cd frontend | |
| pnpm install --frozen-lockfile 2>/dev/null || true | |
| pnpm --filter @whalewhisper/web build | |
| cd .. | |
| fi | |
| git log --oneline -10 | |
| ``` | |
| ### Phase 5: Push | |
| ```bash | |
| git push origin ${{ github.event.inputs.target_branch }} --force-with-lease | |
| ``` | |
| --- | |
| ## Important Rules | |
| 1. **DO** preserve all feature commits from target branch | |
| 2. **DO** use --force-with-lease (not --force) | |
| 3. **DO** verify compilation before pushing | |
| 4. **DO** document any unresolved conflicts | |
| 5. **DO NOT** lose any commits | |
| 6. **DO NOT** push if verification fails | |
| 7. **DO NOT** resolve conflicts you don't understand | |
| claude_args: "--model ${{ vars.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929' }} --max-turns 999 --allowedTools Read,Write,Edit,Bash(*)" | |
| use_commit_signing: false | |
| auto-fix: | |
| if: | | |
| (github.event_name == 'workflow_run' && | |
| github.event.workflow_run.conclusion == 'failure' && | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.head_repository.full_name == github.repository && | |
| !startsWith(github.event.workflow_run.head_branch, 'claude-fix-')) || | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.task_type == 'ci-fix') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| issues: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 0 | |
| - name: Fetch failed logs | |
| id: failed_logs | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT || github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| run_id="${{ github.event.workflow_run.id }}" | |
| run_url="${{ github.event.workflow_run.html_url }}" | |
| if [ -z "$run_id" ]; then | |
| echo "::error::Missing workflow_run.id; this job only supports workflow_run triggers." | |
| exit 1 | |
| fi | |
| echo "Fetching failed logs for run $run_id ($run_url)" | |
| tmp_err="$(mktemp)" | |
| if gh run view "$run_id" --repo "$GH_REPO" --log-failed > failed.log 2>"$tmp_err"; then | |
| if [ ! -s failed.log ]; then | |
| echo "::error::Fetched logs are empty (run $run_id)." | |
| { | |
| echo "ERROR: fetched logs are empty." | |
| echo "Run URL: $run_url" | |
| } > failed.log | |
| exit 1 | |
| fi | |
| else | |
| echo "::error::Failed to fetch logs for run $run_id." | |
| { | |
| echo "ERROR: failed to fetch logs for run $run_id." | |
| echo "Run URL: $run_url" | |
| echo "" | |
| echo "--- gh stderr ---" | |
| cat "$tmp_err" | |
| } > failed.log | |
| { | |
| echo "### Claude CI Auto-Fix" | |
| echo "" | |
| echo "Failed to fetch logs for run $run_id." | |
| echo "" | |
| echo "- Run: $run_url" | |
| echo "- Details captured in \`failed.log\` (runner workspace)" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 1 | |
| fi | |
| echo "log_path=failed.log" >> "$GITHUB_OUTPUT" | |
| - name: Get CI failure details | |
| id: failure_details | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const run = await github.rest.actions.getWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: ${{ github.event.workflow_run.id }} | |
| }); | |
| const jobs = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: ${{ github.event.workflow_run.id }} | |
| }); | |
| const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure'); | |
| const pullRequests = ${{ toJSON(github.event.workflow_run.pull_requests) }}; | |
| const hasPR = pullRequests && pullRequests.length > 0; | |
| return { | |
| runUrl: run.data.html_url, | |
| workflowName: run.data.name, | |
| failedJobs: failedJobs.map(j => j.name), | |
| hasPR: hasPR, | |
| prNumber: hasPR ? pullRequests[0].number : null, | |
| headBranch: '${{ github.event.workflow_run.head_branch }}', | |
| headSha: '${{ github.event.workflow_run.head_sha }}' | |
| }; | |
| - name: Run Claude Code for Auto-Fix | |
| uses: anthropics/claude-code-action@v1 | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} | |
| prompt: | | |
| # Role: CI Failure Auto-Fix Assistant | |
| You are a CI failure auto-fixer for repository ${{ github.repository }}. Your task is to analyze CI failures and apply safe, minimal fixes. | |
| --- | |
| ## Context | |
| - **Workflow**: ${{ fromJSON(steps.failure_details.outputs.result).workflowName }} | |
| - **Failed Run**: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }} | |
| - **Branch**: ${{ fromJSON(steps.failure_details.outputs.result).headBranch }} | |
| - **SHA (pinned)**: ${{ fromJSON(steps.failure_details.outputs.result).headSha }} | |
| - **Has PR**: ${{ fromJSON(steps.failure_details.outputs.result).hasPR }} | |
| - **PR Number**: ${{ fromJSON(steps.failure_details.outputs.result).prNumber }} | |
| - **Failed Jobs**: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }} | |
| --- | |
| ## Error Logs | |
| Read the CI failure logs from the file `failed.log` in the workspace root. This file contains the output from the failed CI jobs. | |
| --- | |
| ## Core Principles | |
| 1. **SAFE FIXES ONLY**: Only fix obvious, mechanical issues. | |
| 2. **NO BEHAVIOR CHANGES**: Never alter application logic or functionality. | |
| 3. **MINIMAL SCOPE**: Fix only what's broken, don't improve or refactor. | |
| 4. **VERIFY FIXES**: Always verify the fix resolves the issue. | |
| 5. **DOCUMENT UNFIXABLE**: If something can't be safely fixed, document why. | |
| --- | |
| ## Error Categorization | |
| | Category | Examples | Auto-Fixable? | | |
| |----------|----------|---------------| | |
| | **Python: Formatting** | ruff format violations | YES - run `ruff format` | | |
| | **Python: Lint** | ruff check violations (unused imports, etc.) | YES - run `ruff check --fix` | | |
| | **Python: Import sort** | isort violations | YES - run `ruff check --select I --fix` | | |
| | **Python: Type errors** | mypy type mismatch | NO - may alter behavior | | |
| | **Python: Syntax** | SyntaxError, IndentationError | MAYBE - if obvious | | |
| | **Frontend: Formatting** | Prettier violations | YES - run formatter | | |
| | **Frontend: Lint** | ESLint violations (unused imports, etc.) | YES - run `pnpm run lint --fix` | | |
| | **Frontend: Type errors** | TypeScript type mismatch | NO - may alter behavior | | |
| | **Build: Missing dependency** | Module not found | NO - needs decision | | |
| | **Test: Assertion failure** | Expected vs actual mismatch | NO - needs investigation | | |
| --- | |
| ## Execution Workflow | |
| ### Phase 1: Categorize Errors | |
| Parse error logs. For each error, record: | |
| 1. File path and line number | |
| 2. Error type and category | |
| 3. Whether it's auto-fixable | |
| 4. Specific fix required | |
| ### Phase 2: Apply Safe Fixes | |
| ```bash | |
| # Backend auto-fixes | |
| if [ -d backend ]; then | |
| cd backend | |
| pip install -e . 2>/dev/null || true | |
| ruff check --fix . 2>/dev/null || true | |
| ruff format . 2>/dev/null || true | |
| cd .. | |
| fi | |
| # Frontend auto-fixes | |
| if [ -d frontend ]; then | |
| cd frontend | |
| pnpm install --frozen-lockfile 2>/dev/null || true | |
| pnpm run lint --fix 2>/dev/null || true | |
| pnpm run format 2>/dev/null || true | |
| cd .. | |
| fi | |
| git diff --stat | |
| ``` | |
| For remaining errors needing manual fixes: | |
| 1. Read the file containing the error | |
| 2. Verify the fix is mechanical and safe | |
| 3. Apply minimal targeted edit | |
| 4. DO NOT change any logic | |
| ### Phase 3: Verification | |
| ```bash | |
| # Backend verification | |
| if [ -d backend ]; then | |
| cd backend | |
| python -m compileall -q app || true | |
| ruff check . || true | |
| cd .. | |
| fi | |
| # Frontend verification | |
| if [ -d frontend ]; then | |
| cd frontend | |
| pnpm --filter @whalewhisper/web build || true | |
| cd .. | |
| fi | |
| git diff --stat | |
| git diff | |
| ``` | |
| ### Phase 4: Self-Reflection | |
| | Check | Question | | |
| |-------|----------| | |
| | Scope | Did I only fix CI errors, nothing else? | | |
| | Safety | Could any of my changes alter behavior? | | |
| | Completeness | Did I fix all the safe-to-fix errors? | | |
| | Documentation | Did I document what couldn't be fixed? | | |
| **If any behavior-altering changes were made, STOP and document instead.** | |
| ### Phase 5: Commit & Push | |
| **For PR (hasPR = true):** | |
| ```bash | |
| git checkout -b claude-fix-pr-${{ fromJSON(steps.failure_details.outputs.result).prNumber }}-${{ github.run_id }} | |
| git add . | |
| git commit -m "fix: auto-fix CI failures | |
| Fixed: | |
| - [List each fix applied] | |
| Not auto-fixable (requires human review): | |
| - [List errors that couldn't be safely fixed] | |
| CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }}" | |
| git push origin claude-fix-pr-${{ fromJSON(steps.failure_details.outputs.result).prNumber }}-${{ github.run_id }} | |
| gh pr create \ | |
| --base ${{ fromJSON(steps.failure_details.outputs.result).headBranch }} \ | |
| --title "Auto-fix CI failures for PR #${{ fromJSON(steps.failure_details.outputs.result).prNumber }}" \ | |
| --body "## CI Auto-Fix | |
| **Original PR**: #${{ fromJSON(steps.failure_details.outputs.result).prNumber }} | |
| **Failed CI Run**: [${{ fromJSON(steps.failure_details.outputs.result).workflowName }}](${{ fromJSON(steps.failure_details.outputs.result).runUrl }}) | |
| ### Fixes Applied | |
| | File | Fix | Type | | |
| |------|-----|------| | |
| | [file] | [what was fixed] | [lint/format/etc] | | |
| ### Not Auto-Fixable | |
| | File | Error | Reason | | |
| |------|-------|--------| | |
| | [file] | [error] | [why not safe to auto-fix] | | |
| --- | |
| *Auto-generated by Claude AI*" | |
| ``` | |
| **For non-PR branch (hasPR = false):** | |
| ```bash | |
| git add . | |
| git commit -m "fix: auto-fix CI failures | |
| Fixed: | |
| - [List each fix] | |
| CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }}" | |
| git push origin ${{ fromJSON(steps.failure_details.outputs.result).headBranch }} | |
| ``` | |
| --- | |
| ## Anti-Patterns to Avoid | |
| | Anti-Pattern | Why It's Bad | What To Do Instead | | |
| |--------------|--------------|-------------------| | |
| | Fixing type errors by casting | Hides real bugs | Document for human review | | |
| | Removing "unused" parameters | May break API contracts | Check usage first | | |
| | Changing test assertions | Masks actual failures | Report for investigation | | |
| | Bulk auto-fix without review | May introduce bugs | Review each change | | |
| | Fixing errors you don't understand | May cause regressions | Document and skip | | |
| claude_args: "--model ${{ vars.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929' }} --max-turns 999 --allowedTools Read,Write,Edit,Bash(*)" | |
| use_commit_signing: false |