Fix Drift #99
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: Fix Drift | |
| on: | |
| workflow_dispatch: | |
| workflow_run: | |
| workflows: ["Drift Tests"] | |
| types: [completed] | |
| branches: [main] | |
| concurrency: | |
| group: drift-fix | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| fix: | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| github.event.workflow_run.conclusion == 'failure' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # issues: write # removed — no longer creating issues on drift failure | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| # Step 0a: Clone ag-ui repo for AG-UI schema drift detection | |
| - name: Clone ag-ui repo | |
| run: git clone --depth 1 https://github.com/ag-ui-protocol/ag-ui.git ../ag-ui | |
| # Step 0b: Configure git identity and create fix branch | |
| - name: Configure git | |
| env: | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| git config user.name "aimock-drift-bot" | |
| git config user.email "drift-bot@copilotkit.ai" | |
| git checkout -B "fix/drift-$(date +%Y-%m-%d)-${RUN_ID}" | |
| # Step 1: Detect drift and produce report | |
| - name: Collect drift report | |
| id: detect | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | |
| run: | | |
| set +e | |
| npx tsx scripts/drift-report-collector.ts | |
| EXIT_CODE=$? | |
| set -e | |
| echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT | |
| if [ "$EXIT_CODE" -eq 2 ]; then | |
| : # critical drift found, continue | |
| elif [ "$EXIT_CODE" -ne 0 ]; then | |
| echo "::error::Collector script crashed with exit code $EXIT_CODE" | |
| exit $EXIT_CODE | |
| fi | |
| # Always upload the report as an artifact | |
| - name: Upload drift report | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: drift-report | |
| path: drift-report.json | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| # Step 2: Exit if no critical drift | |
| - name: Check for critical diffs | |
| id: check | |
| env: | |
| DETECT_EXIT_CODE: ${{ steps.detect.outputs.exit_code }} | |
| run: | | |
| if [ "$DETECT_EXIT_CODE" = "2" ]; then | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| echo "Critical drift detected" | |
| else | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "No critical drift detected (exit code: $DETECT_EXIT_CODE) — skipping fix" | |
| fi | |
| # Step 3: Invoke Claude Code to fix | |
| - name: Auto-fix drift | |
| id: autofix | |
| if: steps.check.outputs.skip != 'true' | |
| continue-on-error: true | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | |
| run: npx tsx scripts/fix-drift.ts | |
| # Upload Claude Code output for debugging | |
| - name: Upload Claude Code logs | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: claude-code-output | |
| path: claude-code-output.log | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| # Step 4: Verify fix independently — only when autofix actually succeeded. | |
| # `autofix` has `continue-on-error: true`, so without these explicit guards | |
| # `success()` would remain true on autofix failure and we'd verify+ship a | |
| # broken state. | |
| - name: Verify conformance | |
| if: steps.autofix.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| run: pnpm test | |
| - name: Verify drift resolved | |
| if: steps.autofix.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | |
| run: pnpm test:drift | |
| # Inject git credentials only when needed for push (persist-credentials: false above) | |
| - name: Configure git for push | |
| if: steps.autofix.outcome == 'success' && success() && steps.check.outputs.skip != 'true' | |
| run: git config --local url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/" | |
| env: | |
| TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Step 5: Create PR on success | |
| - name: Create PR | |
| id: pr | |
| if: steps.autofix.outcome == 'success' && success() && steps.check.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| npx tsx scripts/fix-drift.ts --create-pr | |
| PR_URL=$(gh pr list --head "$(git branch --show-current)" --json url --jq '.[0].url') | |
| if [ -z "$PR_URL" ]; then echo "No PR URL found"; exit 1; fi | |
| echo "url=$PR_URL" >> $GITHUB_OUTPUT | |
| # Step 5.5: Auto-merge the drift fix PR | |
| - name: Auto-merge PR | |
| if: success() && steps.pr.outputs.url != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_URL: ${{ steps.pr.outputs.url }} | |
| run: gh pr merge "$PR_URL" --merge --auto | |
| # Step 7: Slack notification on successful fix | |
| - name: Notify Slack on fix success | |
| if: success() && steps.pr.outputs.url != '' | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
| PR_URL: ${{ steps.pr.outputs.url }} | |
| REPO: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| if [ -z "$SLACK_WEBHOOK" ]; then echo "SLACK_WEBHOOK not set, skipping"; exit 0; fi | |
| MSG="✅ *Drift auto-fix PR created with auto-merge enabled*\nPR: ${PR_URL}\nRun: https://github.com/${REPO}/actions/runs/${RUN_ID}" | |
| PAYLOAD=$(jq -n --arg text "$MSG" '{text: $text}') | |
| curl -sf -X POST "$SLACK_WEBHOOK" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" | |
| # Step 8: Slack notification on fix failure | |
| - name: Notify Slack on fix failure | |
| if: failure() && steps.check.outputs.skip != 'true' | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
| REPO: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| if [ -z "$SLACK_WEBHOOK" ]; then echo "SLACK_WEBHOOK not set, skipping"; exit 0; fi | |
| MSG="❌ *Drift auto-fix failed* — manual intervention needed\nRun: https://github.com/${REPO}/actions/runs/${RUN_ID}" | |
| PAYLOAD=$(jq -n --arg text "$MSG" '{text: $text}') | |
| curl -sf -X POST "$SLACK_WEBHOOK" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" |