Claude On Failure #238
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
| # .github/workflows/claude-on-failure.yml | |
| name: Claude On Failure | |
| on: | |
| workflow_run: | |
| workflows: | |
| - Build | |
| - PR Discovery | |
| - PR Review (2x Independent Opus) | |
| - PR Ingestion (Cherry-Pick) | |
| - Nightly Rebase | |
| - Release | |
| - Upstream Stable Adoption | |
| types: [completed] | |
| # One invocation per failed run — use the workflow_run event id as the concurrency group. | |
| concurrency: | |
| group: claude-on-failure-${{ github.event.workflow_run.id }} | |
| cancel-in-progress: false | |
| permissions: | |
| actions: read | |
| contents: read | |
| issues: write | |
| id-token: write | |
| env: | |
| IF_REPO_ROOT: ${{ github.workspace }} | |
| FAILED_WORKFLOW: ${{ github.event.workflow_run.name }} | |
| FAILED_RUN_ID: ${{ github.event.workflow_run.id }} | |
| FAILED_RUN_URL: ${{ github.event.workflow_run.html_url }} | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Job 1 — gate: autonomy kill-switch check | |
| # --------------------------------------------------------------------------- | |
| gate: | |
| name: Autonomy gate (claude-invoke) | |
| # Only run at all when the triggering workflow actually failed. | |
| if: github.event.workflow_run.conclusion == 'failure' | |
| uses: ./.github/workflows/autonomy-check.yml | |
| with: | |
| requested_action: claude-invoke | |
| # --------------------------------------------------------------------------- | |
| # Job 2 — analyze: download logs, run Claude failure-analysis prompt | |
| # --------------------------------------------------------------------------- | |
| analyze: | |
| name: Analyse failure (${{ github.event.workflow_run.name }}) | |
| needs: gate | |
| if: needs.gate.outputs.proceed == 'true' | |
| runs-on: ubuntu-latest | |
| environment: bot-credentials | |
| outputs: | |
| verdict: ${{ steps.run-analysis.outputs.verdict }} | |
| issue_type: ${{ steps.classify.outputs.issue_type }} | |
| steps: | |
| - name: Generate GH App token | |
| id: app-token | |
| uses: tibdex/github-app-token@v2 | |
| with: | |
| app_id: ${{ secrets.GH_APP_ID }} | |
| private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Download failed run artifacts | |
| run: | | |
| gh run download "${{ env.FAILED_RUN_ID }}" \ | |
| --repo "${{ github.repository }}" \ | |
| --dir /tmp/failed-artifacts/ || true | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Download failed run log | |
| run: | | |
| gh run view "${{ env.FAILED_RUN_ID }}" \ | |
| --repo "${{ github.repository }}" \ | |
| --log-failed > /tmp/failed-run.log 2>&1 || true | |
| # Also write workflow metadata | |
| cat > /tmp/failed-run-meta.json << EOF | |
| { | |
| "workflow_name": "${{ env.FAILED_WORKFLOW }}", | |
| "run_id": "${{ env.FAILED_RUN_ID }}", | |
| "run_url": "${{ env.FAILED_RUN_URL }}", | |
| "conclusion": "${{ github.event.workflow_run.conclusion }}" | |
| } | |
| EOF | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Load failure-analysis prompt | |
| id: load-prompt | |
| shell: bash | |
| run: | | |
| { | |
| echo 'prompt<<__EOF_PROMPT__' | |
| cat .if-fork/prompts/failure-analysis.md | |
| echo | |
| echo '__EOF_PROMPT__' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Run failure analysis (Claude Sonnet) | |
| id: run-analysis | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| prompt: ${{ steps.load-prompt.outputs.prompt }} | |
| claude_args: >- | |
| --max-turns 8 | |
| --model claude-sonnet-4-6 | |
| --allowedTools Read,Bash,Grep,mcp__github__issue_write,mcp__github__search_issues | |
| --disallowedTools Edit,Write,mcp__github__push_files,mcp__github__create_pull_request | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| env: | |
| CONFIG_PATH: .if-fork/config.yaml | |
| LOG_PATH: /tmp/failed-run.log | |
| ARTIFACTS_DIR: /tmp/failed-artifacts/ | |
| FAILED_WORKFLOW_URL: ${{ env.FAILED_RUN_URL }} | |
| ESCALATION_ISSUE_PATH: /tmp/failure-analysis-result.md | |
| FAILED_WORKFLOW_NAME: ${{ env.FAILED_WORKFLOW }} | |
| FAILED_RUN_META: /tmp/failed-run-meta.json | |
| - name: Classify failure for issue type | |
| id: classify | |
| run: | | |
| WORKFLOW="${{ env.FAILED_WORKFLOW }}" | |
| # Default issue type | |
| ISSUE_TYPE="operator-followup" | |
| # Workflow-specific issue types | |
| case "$WORKFLOW" in | |
| "PR Ingestion (Cherry-Pick)") | |
| ISSUE_TYPE="cherry-pick-failure" | |
| ;; | |
| "Build") | |
| # Check if analysis result indicates a perf regression | |
| if [ -f /tmp/failure-analysis-result.md ]; then | |
| if grep -qi "perf\|regression\|benchmark" /tmp/failure-analysis-result.md; then | |
| ISSUE_TYPE="perf-regression" | |
| else | |
| ISSUE_TYPE="operator-followup" | |
| fi | |
| fi | |
| ;; | |
| *) | |
| ISSUE_TYPE="operator-followup" | |
| ;; | |
| esac | |
| echo "issue_type=$ISSUE_TYPE" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Failure classified as issue type: $ISSUE_TYPE" | |
| - name: Upload analysis result artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: failure-analysis-${{ env.FAILED_RUN_ID }} | |
| path: /tmp/failure-analysis-result.md | |
| retention-days: 30 | |
| # --------------------------------------------------------------------------- | |
| # Job 3 — record: open GitHub issue, emit failure_analyzed ledger event | |
| # --------------------------------------------------------------------------- | |
| record: | |
| name: Record failure and emit ledger event | |
| needs: [gate, analyze] | |
| if: always() && needs.gate.outputs.proceed == 'true' && needs.analyze.result != 'skipped' | |
| runs-on: ubuntu-latest | |
| environment: bot-credentials | |
| steps: | |
| - name: Generate GH App token | |
| id: app-token | |
| uses: tibdex/github-app-token@v2 | |
| with: | |
| app_id: ${{ secrets.GH_APP_ID }} | |
| private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Download analysis artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: failure-analysis-${{ env.FAILED_RUN_ID }} | |
| path: /tmp/ | |
| continue-on-error: true | |
| - name: Open failure issue | |
| id: open-issue | |
| run: | | |
| ISSUE_TYPE="${{ needs.analyze.outputs.issue_type }}" | |
| WORKFLOW="${{ env.FAILED_WORKFLOW }}" | |
| RUN_URL="${{ env.FAILED_RUN_URL }}" | |
| # Compose issue body | |
| if [ -f /tmp/failure-analysis-result.md ]; then | |
| BODY_FILE=/tmp/failure-analysis-result.md | |
| else | |
| # Fallback minimal body | |
| cat > /tmp/failure-analysis-fallback.md << EOF | |
| ## CI Failure: $WORKFLOW | |
| **Run URL:** $RUN_URL | |
| **Issue type:** $ISSUE_TYPE | |
| Failure analysis did not produce a detailed report. Please investigate the run directly. | |
| EOF | |
| BODY_FILE=/tmp/failure-analysis-fallback.md | |
| fi | |
| # Map issue type to labels | |
| case "$ISSUE_TYPE" in | |
| "cherry-pick-failure") | |
| LABELS="operator-followup,cherry-pick-failure,ci-failure" | |
| ;; | |
| "perf-regression") | |
| LABELS="operator-followup,perf-regression,ci-failure" | |
| ;; | |
| *) | |
| LABELS="operator-followup,ci-failure" | |
| ;; | |
| esac | |
| ISSUE_TITLE="CI failure [$ISSUE_TYPE]: $WORKFLOW — $(date -u +%Y-%m-%d)" | |
| gh issue create \ | |
| --repo "${{ github.repository }}" \ | |
| --title "$ISSUE_TITLE" \ | |
| --body-file "$BODY_FILE" \ | |
| --label "$LABELS" | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Import bot GPG key | |
| uses: ./.github/actions/import-bot-gpg | |
| with: | |
| gpg-private-key: ${{ secrets.GH_BOT_GPG_KEY }} | |
| gpg-key-id: ${{ secrets.GH_BOT_GPG_KEY_ID }} | |
| gpg-fingerprint: ${{ secrets.GH_BOT_GPG_FINGERPRINT }} | |
| - name: Emit failure_analyzed ledger event | |
| run: | | |
| python tools/ledger-event.py \ | |
| --event failure_analyzed \ | |
| --pr-number 0 \ | |
| --head-sha "${{ github.sha }}" \ | |
| --actor claude-on-failure \ | |
| --details-json "{\"workflow\": \"${{ env.FAILED_WORKFLOW }}\", \"failed_run_id\": \"${{ env.FAILED_RUN_ID }}\", \"issue_type\": \"${{ needs.analyze.outputs.issue_type }}\", \"run_url\": \"${{ env.FAILED_RUN_URL }}\"}" \ | |
| --push | |
| env: | |
| GIT_AUTHOR_NAME: "Claude (Initial Force WPF Bot)" | |
| GIT_AUTHOR_EMAIL: "wpf-bot@initialforce.com" | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} |