nyxcore-systems
9 min read

The AutoFix Journey: Orchestrating AI, Git, and Real-time Feedback for Automated Code Remediation

Join me on the journey of building the AutoFix Pipeline – an ambitious system for automated security and bug discovery, AI-powered remediation, and seamless integration with GitHub and real-time UI. It's feature complete, but not without a few battle scars.

AILLMDevOpsSecurityAutomated RemediationGitHub APIPrismaTypeScriptSSEFullstack Development

Just wrapped up a significant development sprint, and the feeling of pushing a massive, feature-complete pipeline to main is always exhilarating. This past session was all about bringing the AutoFix Pipeline to life – a system designed to automate security and bug discovery, leverage AI for remediation, and seamlessly integrate with our existing developer workflows.

The goal was audacious: a full end-to-end solution that could scan a repository, detect issues, generate AI-powered fixes as unified diffs, apply them, optionally create GitHub Pull Requests, and provide real-time feedback. And I'm thrilled to report that, as of commit f50215a, it's feature complete.

Let's dive into what went into building this beast, the challenges we faced, and what comes next.

The Vision: Automated Code Remediation

Imagine a world where common security vulnerabilities, performance bottlenecks, and code smells are not just detected, but automatically fixed with minimal human intervention. That's the promise of AutoFix.

Our pipeline works like this:

  1. Scan & Detect: Analyze a codebase using LLMs to identify issues (OWASP, bugs, perf, error-handling, code smells).
  2. Generate Fixes: For each detected issue, another LLM generates a precise, unified diff patch.
  3. Apply & Preview: The system applies these patches locally, allowing developers to review them.
  4. Action & Integrate: Developers can resolve, skip, or even trigger a GitHub Pull Request directly from the UI.
  5. Knowledge Hub: All remediation actions feed into our Knowledge Hub to improve future insights and resolution rates.

It's about shifting left on security and quality, making developers more efficient, and reducing the overhead of manual fixes.

Under the Hood: Building the AutoFix Pipeline

Bringing this vision to reality required touching almost every part of our stack, from database models to real-time UI components.

1. The Data Foundation: Prisma Models

First, we needed to store the state of our automated runs and the issues they discovered. This meant extending our prisma/schema.prisma:

prisma
model AutoFixRun {
  id           String      @id @default(cuid())
  createdAt    DateTime    @default(now())
  updatedAt    DateTime    @updatedAt
  status       String      // e.g., 'pending', 'running', 'completed', 'cancelled'
  repositoryId String
  tenantId     String
  userId       String
  issues       AutoFixIssue[]

  repository   Repository @relation(fields: [repositoryId], references: [id])
  tenant       Tenant     @relation(fields: [tenantId], references: [id])
  user         User       @relation(fields: [userId], references: [id])
}

model AutoFixIssue {
  id          String       @id @default(cuid())
  createdAt   DateTime     @default(now())
  updatedAt   DateTime     @updatedAt
  runId       String
  severity    String       // e.g., 'critical', 'high', 'medium'
  category    String       // e.g., 'security', 'bug', 'performance'
  status      String       // e.g., 'detected', 'fixed', 'skipped', 'resolved'
  title       String
  description String
  evidence    Json         // Code snippet, file path, line numbers
  patch       String?      // Unified diff string
  prUrl       String?
  resolvedBy  String?      // 'user', 'auto'
  resolvedAt  DateTime?

  run         AutoFixRun   @relation(fields: [runId], references: [id])
}

These models, AutoFixRun and AutoFixIssue, form the backbone, linking runs to repositories, users, and tenants, and detailing each issue with its generated patch. We also defined types like AutoFixEvent, IssueSeverity, IssueCategory, and IssueStatus in src/types/auto-fix.ts for strong typing across the application.

2. GitHub Integration: The Hands of Automation

For creating branches, updating files, and opening Pull Requests, our existing github-connector.ts needed a significant upgrade. We extended ghFetch to support POST/PUT requests and added specific utilities:

  • createBranch(repo, branchName, sha)
  • createOrUpdateFile(repo, filePath, content, commitMessage, branchName, sha)
  • createPullRequest(repo, title, head, base, body)
  • getFileSha(repo, filePath, branch)

These functions are crucial for the "apply patch and create PR" stage of the pipeline.

3. The Brains: LLM-Powered Detection & Fix Generation

This is where the AI magic happens:

  • src/server/services/auto-fix/issue-detector.ts: This service orchestrates batch LLM calls to analyze code segments and detect various issue types, enriching them with severity, category, and evidence.
  • src/server/services/auto-fix/fix-generator.ts: For each detected issue, this service leverages another LLM to generate a precise, unified diff string. This is a critical step, as the quality of these diffs directly impacts developer trust.

4. The Orchestrator: Pipeline & Patching Utilities

With detection and fix generation in place, we needed to stitch it all together:

  • src/server/services/auto-fix/patch-utils.ts: This utility became surprisingly complex. It's responsible for parsing unified diffs into hunk structures and, more importantly, reliably applying these patches to actual code. Handling line endings, context lines, and fuzzy matching is no trivial task.
    typescript
    // Simplified example of applyPatch logic
    import { parseHunks } from './patch-utils';
    
    function applyPatch(originalContent: string, unifiedDiff: string): string {
      const hunks = parseHunks(unifiedDiff);
      let newContent = originalContent.split('\n');
    
      for (const hunk of hunks) {
        // Logic to apply changes from hunk to newContent
        // This involves careful line number tracking and content manipulation
      }
      return newContent.join('\n');
    }
    
  • src/server/services/auto-fix/pipeline.ts: This is the heart of the operation, an AsyncGenerator orchestrating the entire flow: scan → detect → fix → PR. Using an AsyncGenerator allows us to stream progress updates back to the client in real-time, which is essential for a long-running process.

5. API & Real-time Feedback

For user interaction and real-time updates:

  • src/server/trpc/routers/auto-fix.ts: A new tRPC router exposes 7 procedures: list, get, start (a new run), cancel, resolveIssue, skipIssue, and resolutionStats. This provides a robust API for our UI.
  • src/app/api/v1/events/auto-fix/[id]/route.ts: An SSE (Server-Sent Events) streaming endpoint. This is how the UI gets live updates on the pipeline's progress, issue detections, and fix generations without constant polling.
  • src/app/api/v1/webhooks/auto-fix/resolve/route.ts: An external webhook for other AI tools or external systems to mark issues as resolved programmatically.

6. The User Interface

A powerful backend is useless without a great frontend. We built a suite of components and pages:

  • src/components/auto-fix/patch-viewer.tsx: A syntax-highlighted viewer for unified diffs, making it easy for developers to review proposed changes.
  • src/components/auto-fix/issue-card.tsx: Expandable cards displaying issue details, evidence, the patch, and actions (resolve/skip).
  • src/components/auto-fix/run-progress.tsx: An SSE-powered progress bar showing the current phase of an AutoFix run.
  • src/components/auto-fix/run-stats.tsx: Cards displaying key metrics like issues detected, fixes generated, PRs created, and resolution rates.
  • src/app/(dashboard)/dashboard/auto-fix/page.tsx: The main AutoFix dashboard, listing all runs and providing a dialog to start a new scan.
  • src/app/(dashboard)/dashboard/auto-fix/[id]/page.tsx: The run detail page, featuring live SSE updates, filters for issues, and the list of detected issues.

Finally, we integrated getResolutionStats() into our knowledge-hub.ts and knowledge-stats.tsx to blend auto-fix resolution rates with other insight pairings, providing a holistic view of code quality improvements. A new "AutoFix" sidebar link with a Wrench icon now sits proudly in our sidebar.tsx.

Navigating the Treacherous Waters: Lessons Learned

No significant feature ships without a few battle scars. Here are some of the "pain points" and the lessons they taught me:

1. Prisma and Unsupported Database Types: The Embedding Vector Saga

  • The Problem: Our workflow_insights table has an embedding vector(1536) column, which is a PostgreSQL-specific type. When running npm run db:push (which essentially runs prisma db push), Prisma warns about dropping this unsupported column. If you don't use --accept-data-loss, it fails. If you do, it drops it.
  • The Workaround: I had to use prisma db push --accept-data-loss and then immediately re-add the column using a raw SQL ALTER TABLE workflow_insights ADD COLUMN IF NOT EXISTS embedding vector(1536); command.
  • Lesson Learned: When working with ORMs and database-specific types, always be aware of how your ORM handles "unsupported" types during schema migrations. Plan for manual intervention or consider alternative storage for such columns if they become too problematic. This is a recurring issue I need to address more robustly in the future.

2. Relative Path Depth in API Routes: A Classic Blunder

  • The Problem: When setting up the SSE endpoint (src/app/api/v1/events/auto-fix/[id]/route.ts), I incorrectly imported ../../../../middleware for shared middleware logic. TypeScript's module resolution quickly (and correctly) pointed out TS2307: Cannot find module.
  • The Workaround: A quick fix to ../../../middleware (3 levels up) resolved it.
  • Lesson Learned: This is a common developer hiccup. Relative paths can be a pain, especially when nesting API routes. Establishing clear conventions or using path aliases (like in tsconfig.json) can help prevent these trivial but frustrating errors. Consistency with existing patterns (like our code-analysis SSE endpoint) is key.

3. ESLint Configuration Woes: The Silent Build Blocker

  • The Problem: Running npm run build (which includes linting) failed due to ESLint not finding the @typescript-eslint/no-unused-vars rule across all files. This wasn't a new issue specific to AutoFix, but a pre-existing configuration problem that next build surfaced globally.
  • The Workaround: For now, I had to use npx next build --no-lint to get a successful build. The only remaining build error is a pre-existing useSearchParams() Suspense boundary warning in another part of the application.
  • Lesson Learned: A broken linting setup is a ticking time bomb. It undermines code quality and can block deployments. Prioritize fixing your ESLint configuration. A robust linting setup acts as an early warning system, catching issues before they become build blockers.

What's Next? Immediate Post-Launch Steps

Even though it's "feature complete," the work isn't truly done. Here are the immediate next steps:

  1. End-to-end Testing: Start an AutoFix run on a test repository via the UI and verify that SSE streaming works, issues are detected, fixes generated, and PRs created as expected.
  2. Webhook Testing: Manually curl the /api/v1/webhooks/auto-fix/resolve endpoint with a valid issueId to ensure external resolution works.
  3. Row-Level Security (RLS): Implement RLS policies for auto_fix_runs and auto_fix_issues in prisma/rls.sql to ensure data isolation between tenants.
  4. Fix Pre-existing ESLint: Dedicate time to properly configure the @typescript-eslint plugin.
  5. Address Suspense Warning: Resolve the useSearchParams() Suspense boundary issue in the consolidation/new page.
  6. Unit Tests: Consider adding specific unit tests for patch-utils.ts (especially applyPatch and parseHunks) given its critical role in applying changes safely.

Shipping the AutoFix Pipeline has been an intense but incredibly rewarding experience. It's exciting to imagine the impact this will have on developer productivity and code quality. The journey of building robust, AI-powered developer tools is full of challenges, but the potential rewards are immense.


json
{
  "thingsDone": [
    "Implemented AutoFixRun and AutoFixIssue Prisma models",
    "Extended GitHub connector for write operations (branches, files, PRs)",
    "Created LLM-based issue detection service",
    "Created LLM-based unified diff fix generation service",
    "Developed unified diff parser and applier utilities",
    "Built AsyncGenerator-based pipeline orchestrator (scan -> detect -> fix -> PR)",
    "Exposed tRPC procedures for AutoFix management",
    "Created SSE streaming endpoint for real-time progress updates",
    "Implemented external resolution webhook for AutoFix issues",
    "Developed UI components: Patch Viewer, Issue Card, Run Progress, Run Stats",
    "Created AutoFix dashboard and run detail pages",
    "Integrated AutoFix resolution stats into Knowledge Hub",
    "Added AutoFix sidebar navigation link"
  ],
  "pains": [
    "Prisma 'unsupported type' warning for PostgreSQL vector column requiring --accept-data-loss and manual ALTER TABLE",
    "Incorrect relative path depth in SSE endpoint import (`../../../../middleware` vs `../../../middleware`)",
    "Pre-existing ESLint configuration issue (`@typescript-eslint/no-unused-vars` not found) blocking build, requiring `--no-lint` workaround"
  ],
  "successes": [
    "Achieved feature complete status for the entire AutoFix Pipeline",
    "Successfully integrated AI (LLMs) for both detection and remediation",
    "Implemented real-time feedback mechanisms using SSE for long-running processes",
    "Built a comprehensive UI for managing and interacting with automated fixes",
    "Established a robust backend architecture using tRPC and services"
  ],
  "techStack": [
    "TypeScript",
    "Next.js",
    "React",
    "Prisma",
    "PostgreSQL",
    "tRPC",
    "LLMs (AI)",
    "GitHub API",
    "SSE (Server-Sent Events)",
    "ESLint"
  ]
}