AI EngineeringDevOpsCI/CDTestingAutomation

Self-Healing Code: Auto-Fixing Pipelines

D
DevOps Engineer
Featured Guide 18 min read

Red builds are opportunities.

Traditionally, a failed test meant a Developer context-switch. They have to pull the branch, run the test, finding the typo.

Self-Healing Pipelines intercept the error output, specificially the Stack Trace, and ask an LLM: "Given this code and this error, what is the fix?"

02. Authentication Loop

The keys is to pass the file content and the error message. The LLM returns a git-patch.

Deep Dive: The Agent's Prompt

You must constrain the agent to be chirurgic.

System: You are a CI Repair Bot.
Input: [Bad Code] + [Stack Trace]
Output: ONLY the unified diff patch. Do not output markdown. Do not rewrite the whole file.

// ci-healer.ts
if
(exitCode !== 0) {'{'}
  
const
patch =
await
agent.fix({'{'}
    code: readFileSync('app.ts'),
    error: stderr
  {'}'});

  applyPatch(patch);
  rerunTests();
{'}'}

04. The Senior Engineer's Take

Don't Auto-Merge

AI fixes are often correct in syntax but wrong in logic (e.g., deleting the test to make it pass).

Rule: The AI commits the fix as a "Suggestion" commit. A human must still press the "Merge" button.

The Flaky Test Trap

If your test suite is flaky (fails 1% of the time randomly), do not use self-healing agents. The agent will hallucinate a "fix" for code that wasn't broken, introducing zombie code into your repo. Fix the tests first.

Interactive Playground

import React, { useState } from 'react';

// 🩹 Self-Healing Viz

export default function HealerDemo() {
    const [step, setStep] = useState(0); // 0: Idle, 1: Run Test (Fail), 2: Analyze, 3: Patch, 4: Re-Run (Pass)

    const runPipeline = async () => {
        setStep(1);
        await wait(1500); // Fail
        setStep(2);
        await wait(1500); // Analysis
        setStep(3);
        await wait(1500); // Patch
        setStep(4);
        await wait(1500); // Pass
        setStep(5);
    };
    
    const wait = (ms) => new Promise(r => setTimeout(r, ms));

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-red-500">🩹</span> Self-Healing CI
                </h3>
                <button 
                    onClick={runPipeline}
                    disabled={step > 0 && step < 5}
                    className="bg-red-500 hover:bg-red-600 disabled:opacity-50 text-white font-bold px-6 py-2 rounded-xl transition-all flex items-center gap-2"
                >
                    {step > 0 && step < 5 ? <span className="animate-spin"></span> : <span>▶️</span>}
                    {step > 0 && step < 5 ? 'Running...' : 'Break Build'}
                </button>
            </div>

            <div className="flex gap-6">
                
                {/* Timeline */}
                <div className="w-1/3 space-y-4">
                     <StepItem active={step === 1} done={step > 1} label="Running Unit Tests" error={step === 1} />
                     <StepItem active={step === 2} done={step > 2} label="AI Analysis (Stack Trace)" />
                     <StepItem active={step === 3} done={step > 3} label="Applying Code Patch" />
                     <StepItem active={step === 4} done={step > 4} label="Re-Running Tests" success={step === 4 || step === 5} />
                </div>

                {/* Console Output */}
                <div className="flex-1 bg-black rounded-xl p-6 font-mono text-xs overflow-hidden flex flex-col">
                    <div className="flex items-center gap-2 text-gray-500 border-b border-gray-800 pb-2 mb-4">
                        <span>💻</span> /bin/zsh
                    </div>
                    
                    <div className="space-y-2 text-gray-300">
                        {step >= 1 && (
                            <div className="animate-in fade-in">
                                $ npm test <br/>
                                > Tests started... <br/>
                                <span className="text-red-500">x Failed: Expected 200, got 500</span> <br/>
                                <span className="text-gray-500">  at calculateTotal (app.ts:45:10)</span>
                            </div>
                        )}

                        {step >= 2 && (
                            <div className="animate-in fade-in mt-4 border-t border-gray-800 pt-2 text-blue-400">
                                [AI Agent] Detecting failure... Reading context... <br/>
                                [AI Agent] Bug found: Division by zero in discount logic.
                            </div>
                        )}

                        {step >= 3 && (
                            <div className="animate-in fade-in mt-4 text-yellow-400">
                                [Git] Creating branch 'fix/auto-patch-001'... <br/>
                                [Git] Applying patch... <br/>
                                + if (count === 0) return 0;
                            </div>
                        )}
                        
                        {step >= 4 && (
                            <div className="animate-in fade-in mt-4 border-t border-gray-800 pt-2">
                                $ npm test --retry <br/>
                                > Tests started... <br/>
                                <span className="text-green-500">✓ calculateTotal passed</span> <br/>
                                <span className="text-green-500">✓ All tests passed (14ms)</span>
                            </div>
                        )}

                    </div>
                </div>

            </div>
        </div>
    );
}

function StepItem({ active, done, label, error, success }) {
    return (
        <div className={`p-4 rounded-xl border transition-all ${
            active 
            ? 'bg-white dark:bg-slate-900 border-blue-500 shadow-lg scale-105' 
            : done 
                ? 'bg-gray-50 dark:bg-slate-800/50 border-gray-200 dark:border-slate-800 opacity-50'
                : 'bg-transparent border-transparent opacity-30'
        }`}>
            <div className="flex items-center gap-3">
                {done ? (
                     <span className="text-2xl"></span>
                ) : active ? (
                    error ? <span className="text-2xl"></span> : <span className="text-2xl animate-spin"></span>
                ) : (
                    <div className="w-5 h-5 rounded-full border-2 border-gray-300"></div>
                )}
                
                <span className={`font-bold ${error ? 'text-red-500' : success ? 'text-green-500' : 'text-gray-700 dark:text-gray-200'}`}>{label}</span>
            </div>
        </div>
    );
}