CI Broke Again Over a Lint Error—Can AI Just Fix It?

🌏 閱讀中文版本


CI Is Red. Again.

You just pushed a commit.

Switch to the browser, open GitHub Actions, and there it is—the familiar red X.

Click in, wait for the logs to load, scroll all the way down—

error: 'response' is defined but never used  no-unused-vars

An unused import.

You sigh, switch back to the IDE, delete the line, commit, push, and wait another 5 minutes.

How many times does this happen per week? 10-15 minutes each time.

If it happens 3 times a day, that’s 15-20 hours per month—spent on problems anyone could fix.

This article isn’t about writing better code to prevent CI failures (that’s a different conversation).

This is about something else: for mechanical failures with deterministic fixes, why not let AI handle them?


⚡ TL;DR

  • Pain point: The CI fix loop (wait → copy error → fix → wait) takes 10-15 min each time, mostly for mechanical issues
  • Solution: Use Claude Code GitHub Action to auto-detect and fix lint, type errors, and formatting issues
  • Safety is key: Allowlist (can auto-fix) vs denylist (requires human review) is the core design
  • Good for: Teams with CI pipelines where lint/format/type check failures are frequent
  • Not for: Scenarios where CI failures are mainly logic errors or security-related

Where Your Time Goes: Two Types of CI Failures

Not all CI failures are the same.

Distinguishing these two types is the prerequisite for deciding whether to automate:

Mechanical Failures Logic Failures
Examples Unused import, inconsistent formatting, typo Business logic error, race condition
Fix Certainty High—almost always one correct fix Low—requires understanding context
Fix Time Seconds (but waiting for CI takes 5-10 min) Minutes to hours
Risk Very low—removing an unused import won’t break anything High—wrong fix may introduce new bugs
Who Can Fix Anyone Someone who knows the codebase

Key Insight: Mechanical failures have deterministic fixes and very low risk—that’s the automation sweet spot. Not all CI failures should be auto-fixed, but mechanical ones clearly deserve it.

You might ask: why not just catch these with pre-commit hooks?

Good question. Pre-commit hooks do catch some of these, but in practice:

  • Some checks are too slow for pre-commit (type checking, integration tests)
  • Team members sometimes skip with --no-verify
  • CI and local environments differ—some errors only surface in CI

So CI-side auto-fix isn’t replacing pre-commit. It’s the last line of defense.


YOLO Push: A Name That Sounds Reckless

This concept comes from the Claude Code Meetup Taipei in January 2026, shared by Anthropic’s Oliver Wang.

Inside Anthropic, they have a practice: when CI fails, automatically trigger Claude Code to fix it.

They call it YOLO Push—”You Only Live Once.”

Sounds careless.

But the underlying architecture is actually rigorous.

The spirit of YOLO isn’t “just fix whatever.” It’s:

For known mechanical issues, humans shouldn’t waste time in the “wait → copy → fix → wait” loop.

The core safety mechanism is an allowlist and a denylist—strictly defining what can be auto-fixed and what can’t.

Let’s break that down.


Safety Boundaries: Allowlist vs Denylist

This is the most critical design decision in the entire approach.

It’s not about “should we auto-fix?” It’s about “what can we auto-fix, and what absolutely cannot be touched?

Allowlist ✅ (Can Auto-Fix)

Type Examples
Lint errors Unused import, missing semicolon
Formatting Indentation, trailing whitespace
Type errors (simple) Missing type annotation, wrong return type
Variable naming Typo in variable name

Denylist ❌ (Requires Human Review)

Type Why It Can’t Be Auto-Fixed
Security vulnerabilities Fix may affect the security model
Auth/authorization One wrong line could open unauthorized access
Data access logic Could leak or corrupt user data
Business logic Requires domain knowledge to judge
Compliance-related Regulations require human confirmation

Key Insight: Denylist items always require human review, no matter how correct the AI’s fix looks. This isn’t a technical issue—it’s risk management: a wrong mechanical fix means CI runs again; a wrong security fix could be an incident.

Taste vs Correctness

There’s also a gray area worth noting:

Objective (Good for Auto-Fix) Subjective (Needs Human Judgment)
Syntax errors Should this function be extracted?
Type mismatch Is this comment useful?
Inconsistent formatting Is this abstraction over-engineered?
Unused import Is this naming good enough?

Claude’s training data comes from repos with vastly different styles. What’s good style for a startup isn’t good style for a financial system.

You can automate correctness. You can’t automate taste.


Implementation: Two Approaches

Approach A: Local CLI + GitHub MCP (Simplest)

🟢 Difficulty: Easy ⏱️ Time: 5 minutes 📋 Prerequisites: Claude Code CLI installed, GitHub MCP server configured

GitHub MCP Setup: If you haven’t set it up yet, run /add-mcp-server in Claude Code and select GitHub. Or manually add to ~/.claude/mcp.json:

{
  "mcpServers": {
    "github": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    }
  }
}

You’ll need a GITHUB_TOKEN environment variable (GitHub Personal Access Token).

If you just want to quickly fix a CI failure without a full automation pipeline, the simplest approach:

claude "CI is failing on main. Figure out why, fix it, \
       commit & push, and monitor to be sure your fix worked."

Claude Code will:

  1. Check CI failure logs via GitHub MCP
  2. Analyze the error
  3. Modify the code
  4. Run lint/build locally to confirm
  5. Commit + push
  6. Monitor whether CI passes
  7. If it still fails, keep trying

Pros: Zero setup. Works immediately.

Cons: Requires manual triggering. Occupies your local Claude Code session.

Tip: Use git worktree to avoid Claude occupying your working directory while fixing CI.


Approach B: GitHub Action Auto-Trigger (Full Automation)

🟡 Difficulty: Medium ⏱️ Time: 30 minutes 📋 Prerequisites: GitHub repo admin access, Anthropic API key

This is based on Anthropic’s official ci-failure-auto-fix.yml example—automatically triggers Claude to fix when CI fails.

Step 1: Install the Claude GitHub App

The Claude GitHub App provides repo read access and PR interaction capabilities—it’s the foundation for claude-code-action to work.

/install-github-app

Or install manually: github.com/apps/claude

After installation, confirm the app is authorized for your target repo.

Step 2: Set Up the API Key

In your GitHub repo → Settings → Secrets → Actions, add:

  • ANTHROPIC_API_KEY: Your Claude API key

Step 3: Create the Workflow File

Create .github/workflows/auto-fix-ci.yml:

How to find your CI workflow name? Open your .github/workflows/ directory, find your CI YAML file, and look at the name: field at the top. For example, if it says name: CI, use "CI". If it says name: Build and Test, use "Build and Test".

name: Auto Fix CI Failures

on:
  workflow_run:
    workflows: ["CI"]  # Replace with your CI workflow's name field value
    types:
      - completed

permissions:
  contents: write
  pull-requests: write
  actions: read
  issues: write
  id-token: write

jobs:
  auto-fix:
    # Three conditions: CI failed + has a PR + not an auto-fix branch (prevent infinite loop)
    if: |
      github.event.workflow_run.conclusion == 'failure' &&
      github.event.workflow_run.pull_requests[0] &&
      !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ github.event.workflow_run.head_branch }}
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup git identity
        run: |
          git config --global user.email "claude[bot]@users.noreply.github.com"
          git config --global user.name "claude[bot]"

      - name: Create fix branch
        id: branch
        run: |
          BRANCH_NAME="claude-auto-fix-ci-${{ github.event.workflow_run.head_branch }}-${{ github.run_id }}"
          git checkout -b "$BRANCH_NAME"
          echo "branch_name=$BRANCH_NAME" >> $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');

            let errorLogs = [];
            for (const job of failedJobs) {
              const logs = await github.rest.actions
                .downloadJobLogsForWorkflowRun({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  job_id: job.id
                });
              errorLogs.push({
                jobName: job.name,
                logs: logs.data
              });
            }

            return {
              runUrl: run.data.html_url,
              failedJobs: failedJobs.map(j => j.name),
              errorLogs: errorLogs
            };

      - name: Fix CI failures with Claude
        uses: anthropics/claude-code-action@v1
        with:
          prompt: |
            /fix-ci
            CI workflow failed.
            Failed Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }}
            Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }}
            PR Number: ${{ github.event.workflow_run.pull_requests[0].number }}
            Branch Name: ${{ steps.branch.outputs.branch_name }}
            Base Branch: ${{ github.event.workflow_run.head_branch }}
            Repository: ${{ github.repository }}

            Error logs:
            ${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }}

            ONLY fix these types of issues:
            - Linting errors (unused imports, missing semicolons)
            - Type errors (missing annotations, wrong types)
            - Formatting issues (indentation, whitespace)
            - Simple typos in variable names

            DO NOT fix anything related to:
            - Security vulnerabilities
            - Authentication or authorization logic
            - Business logic or data access
            - Test assertions (the test might be correct)

            If the issue is NOT in the allowed list,
            just comment "Requires human review" and stop.
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: >-
            --max-turns 6
            --allowedTools
            "Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(npm:*),Bash(npx:*),Bash(gh:*),Bash(bun:*)"

Step 4: Verify the Workflow

After committing the YAML file to your repo’s main branch:

  1. Open a PR and deliberately introduce a lint error (e.g., const unused = 123;)
  2. Wait for CI to fail
  3. Check the GitHub Actions page—you should see the Auto Fix CI Failures workflow trigger
  4. After Claude fixes the issue, it will create a PR with a claude-auto-fix-ci- prefix
  5. Review the fix and merge if it looks correct

If the workflow doesn’t trigger, check:

  • Whether the workflows: name exactly matches your CI workflow’s name: field
  • Whether the Claude GitHub App is authorized for this repo
  • Whether the ANTHROPIC_API_KEY secret is set

Key Design Decisions

Infinite loop prevention: !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-') ensures auto-fix branches don’t trigger another auto-fix.

/fix-ci skill: A built-in Claude Code skill for CI repair. Combined with context variables like PR Number and Branch Name, it lets Claude pinpoint the issue precisely.

Tool restrictions: --allowedTools strictly limits Claude to file editing, git operations, and the gh CLI. No arbitrary command execution. Bash(gh:*) lets Claude read PR info and CI logs.

Denylist in the prompt: Explicitly tells Claude what not to fix. When it encounters a denylist issue, it stops.

Separate branch + auto PR: The workflow creates a claude-auto-fix-ci-* branch. After fixing, Claude uses gh pr create via Bash(gh:*) to create a PR back to the original branch. You just review and merge—no manual steps needed.


Common Mistakes

Mistake Symptom Solution
Infinite loop Auto-fix triggers new CI → fails again → triggers again Add branch prefix check (included in example)
API cost spike Every CI failure calls the API Add --max-turns limit, set workflow timeout
Fixing the wrong thing Claude changes something it shouldn’t Use separate branch + PR review, never push directly
Insufficient permissions Action can’t push or create PR Verify permissions settings are correct

Adoption Strategy: Conservative to Aggressive

No need to go all-in from day one. Three phases:

Phase 1: Lint and Format Only (🟢 Low Risk)

# In the prompt, only allow
ONLY fix: Linting errors, Formatting issues
DO NOT fix: Everything else

Impact: The safest starting point. Lint and format fixes almost never introduce problems.

Track these metrics:

  • What % of CI failures are auto-resolved?
  • Has auto-fix introduced any new issues?

Phase 2: Add Type Errors (🟡 Medium Risk)

# Expand the allowlist
ONLY fix: Linting errors, Formatting issues, Simple type errors

Note: Type error fixes sometimes have more than one valid approach. Consider switching to PR creation (rather than direct push) at this stage so humans can review.

Phase 3: PR Auto-Fix (🔴 Needs Review Process)

Advanced use case—when linter bots or security scanners leave comments on a PR, auto-fix them.

The PR Autofix with Claude Code action on GitHub Marketplace can handle this.

Recommendation: Even at Phase 3, denylist items always require human review.

Key Insight: The biggest risk in adopting automation isn’t technical—it’s trust boundary management. Start with the smallest allowlist, prove reliability with data, then gradually expand. Same principle as managing any AI Agent.


Cost Considerations

Things to know before you start:

Item Details
GitHub Actions minutes Each trigger consumes runner time, depends on fix complexity
Anthropic API cost Each call consumes tokens; Claude Sonnet is cheaper
Setup time Approach A is near-zero; Approach B is about 30 minutes

Cost-saving tips:

  • Adjust --max-turns (example uses 6) to prevent Claude from trying indefinitely
  • Set workflow timeout-minutes: 10
  • Use claude_args: "--model claude-sonnet-4-6" to choose a cheaper model
  • Consider GitHub concurrency controls to limit simultaneous fix runs

Next Steps

If you want to try this:

Today (5 minutes):

  1. Next time CI fails, try claude "CI is failing..." from your local CLI
  2. Observe Claude’s fix process and quality

This week (30 minutes):

  1. Deploy Approach B’s workflow on a low-risk repo
  2. Only allow lint and format fixes (Phase 1)
  3. Run for two weeks, track success rate

Not sure yet? Answer these 3 questions:

  1. What % of your CI failures are lint/format/typo? (If < 20%, limited benefit)
  2. How does your team feel about AI auto-committing? (Needs discussion)
  3. How long does your CI pipeline take? (The longer, the more time auto-fix saves)

At the end of the day, YOLO Push isn’t about “letting AI replace you.”

It’s about freeing you from the “wait → copy → fix → wait” loop, so you can spend your time on things that actually need your judgment.


Further Reading

Want to dig deeper into Claude Code in engineering practice? These might help:


Sources

Claude Code GitHub Action

CI Auto-Fix in Practice

Official Documentation

Meetup Source

  • Claude Code Meetup Taipei (2026-01-14) – Oliver Wang’s talk

    The original source of the YOLO Push concept. Anthropic internally handles hundreds of mechanical CI fix commits daily.

Leave a Comment