Auto-Fix Pipeline
12. Auto-Fix Pipeline
The Auto-Fix pipeline is a 4-phase automated remediation system that scans GitHub repositories, detects issues via LLM analysis, generates unified-diff patches, and optionally creates pull requests -- all streamed in real time over SSE.
Pipeline Architecture
Entry Point
The pipeline is orchestrated by runAutoFix() in src/server/services/auto-fix/pipeline.ts, an AsyncGenerator<AutoFixEvent> consumed by the SSE route at /api/v1/events/auto-fix/[id].
export async function* runAutoFix(
opts: AutoFixRunOptions
): AsyncGenerator<AutoFixEvent>
AutoFixRunOptions
| Field | Type | Description |
|---|---|---|
runId |
string |
UUID of the AutoFixRun record |
repositoryId |
string |
Target repository |
tenantId |
string |
Tenant scope |
userId |
string |
Initiating user |
provider |
string? |
LLM provider (default: "anthropic") |
model |
string? |
Model override |
autoCreatePR |
boolean? |
Whether to execute Phase 4 |
categories |
IssueCategory[]? |
Filter to specific issue types |
Phase 1: Repository Scan
Delegates to scanRepository() from the code-analysis scanner. If RepositoryFile records already exist for the repository, the scan is skipped and existing file metadata is reused. Otherwise, the scanner fetches the GitHub tree via fetchRepoTree() and indexes files in batches of 50.
Status transition: pending -> scanning
Phase 2: Issue Detection
Delegates to detectIssues(), which sends batches of source file content to the LLM and parses structured JSON responses. Each detected issue is persisted as an AutoFixIssue record.
Status transition: scanning -> detecting
If zero issues are found, the pipeline completes early with a clean-repository message.
Issue Categories
The 5 supported categories are validated at the tRPC layer:
| Category | Description |
|---|---|
security |
Vulnerabilities, injection risks, secrets exposure |
bug |
Logic errors, null dereferences, race conditions |
performance |
N+1 queries, unnecessary allocations, blocking I/O |
error-handling |
Missing try/catch, swallowed errors, unhelpful messages |
code-smell |
Dead code, excessive complexity, naming issues |
Severity Levels
Each issue is assigned a severity: critical, high, medium, or low. The UI renders these with color-coded badges via the severityVariant helper:
critical->danger(red)high->warning(orange)medium->accent(blue)low->default(gray)
Phase 3: Fix Generation
Delegates to generateFixes(), which iterates over AutoFixIssue records with fixStatus: "pending" and calls the LLM to produce a unified diff (fixPatch) and human-readable explanation (fixExplanation).
Status transition: detecting -> fixing
Each generated fix updates the issue record:
// On success:
{ fixStatus: "fix_generated", fixPatch: "...", fixExplanation: "..." }
// On failure:
{ fixStatus: "failed" }
Phase 4: PR Creation (Optional)
Only runs when autoCreatePR: true and at least one fix was generated. For each issue with fixStatus: "fix_generated":
- Create a branch:
autofix/{runIdShort}/{slug}(slug is title, max 30 chars) - Fetch current file content from GitHub
- Apply the unified diff via
applyPatch(original, fixPatch) - Upload patched file with commit message
fix(autofix): {title} - Create PR with structured body containing category, severity, issue detail, and fix explanation
Status transition: fixing -> creating_prs
// Branch naming pattern
const branchName = `autofix/${runId.slice(0, 8)}/${slug}`;
The PR body is auto-generated:
## AutoFix: {title}
**Category:** {category}
**Severity:** {severity}
### Issue
{detail}
### Fix
{fixExplanation}
---
*Generated by nyxCore AutoFix*
Standalone PR Creation
The createPRsForRun() function in src/server/services/auto-fix/create-prs.ts can be called independently from the retryPRs tRPC mutation, enabling users to create PRs after the main pipeline has completed.
Returns a PRCreationResult:
interface PRCreationResult {
created: number;
failed: number;
skipped: number; // Patch could not be applied
results: {
issueId: string;
title: string;
status: "created" | "failed" | "skipped";
prUrl?: string;
prNumber?: number;
error?: string;
}[];
}
Retry Logic
Retry Fixes
retryFixesForRun() in src/server/services/auto-fix/retry-fixes.ts resets pending or failed issues back to pending (clearing fixPatch and fixExplanation), sets the run status to "fixing", and re-runs generateFixes(). On completion, restores status to "completed".
Supports targeting specific issues via issueIds and switching provider/model:
interface RetryFixOptions {
runId: string;
tenantId: string;
issueIds?: string[]; // Optional subset
provider?: string;
model?: string;
}
Retry PRs
The retryPRs mutation filters for issues with fixStatus: "fix_generated" and delegates to createPRsForRun(). Requires at least one eligible issue.
Orphaned Run Cleanup
When a new run is started via the start mutation, any existing runs for the same repository that have been stuck in an active status (scanning, detecting, fixing, creating_prs) for over 10 minutes are automatically marked as failed:
await ctx.prisma.autoFixRun.updateMany({
where: {
repositoryId: input.repositoryId,
status: { in: ["scanning", "detecting", "fixing", "creating_prs"] },
updatedAt: { lt: new Date(Date.now() - 10 * 60 * 1000) },
},
data: {
status: "failed",
errorMessage: "Orphaned: process terminated without completing",
},
});
NerdStats
Every event emitted by the pipeline includes a cumulative nerdStats snapshot:
interface NerdStatsData {
tokenUsage: { prompt: number; completion: number; total: number };
costEstimate: number;
llmCalls: number;
model: string | null;
provider: string | null;
phases: Record<string, {
tokenUsage: { prompt: number; completion: number; total: number };
costEstimate: number;
llmCalls: number;
startedAt: number | null;
completedAt: number | null;
}>;
}
Phase-level breakdown tracks detect, fix, and pr independently. The withNerd() helper attaches a structuredClone of the current stats to every yielded event.
Insight Extraction
After pipeline completion, extractAutoFixInsights() runs as a best-effort operation to persist learnings from the run into the WorkflowInsight table for future pipeline reuse. Failures are silently caught.
Data Model
AutoFixRun
| Column | Type | Description |
|---|---|---|
id |
UUID |
Primary key |
tenantId |
UUID |
Tenant scope |
userId |
UUID |
Initiating user |
repositoryId |
UUID |
Target repository |
status |
String |
pending / scanning / detecting / fixing / creating_prs / completed / failed / cancelled |
config |
Json? |
{ provider, model, autoCreatePR, categories } |
stats |
Json? |
{ filesScanned, issuesFound, fixesGenerated, prsCreated, nerdStats } |
errorMessage |
Text? |
Error description on failure |
startedAt |
DateTime? |
Pipeline start time |
completedAt |
DateTime? |
Pipeline completion time |
AutoFixIssue
| Column | Type | Description |
|---|---|---|
id |
UUID |
Primary key |
runId |
UUID |
Parent run |
category |
String |
One of the 5 issue categories |
severity |
String |
critical / high / medium / low |
title |
String |
Short issue description |
detail |
Text |
Full issue explanation |
filePath |
String |
Affected file |
lineStart / lineEnd |
Int? |
Line range |
evidence |
String[] |
Code snippets proving the issue |
fixStatus |
String |
pending / fix_generated / pr_created / resolved / skipped / failed |
fixPatch |
Text? |
Unified diff |
fixExplanation |
Text? |
Human-readable fix description |
prUrl |
String? |
GitHub PR URL |
prNumber |
Int? |
GitHub PR number |
resolvedBy |
String? |
auto / webhook / manual / claude-code |
tRPC Router
The autoFixRouter in src/server/trpc/routers/auto-fix.ts exposes:
| Procedure | Type | Rate Limit | Description |
|---|---|---|---|
list |
query | standard | Paginated runs for tenant |
get |
query | standard | Single run with all issues |
start |
mutation | LLM | Create new run (cleans orphans first) |
cancel |
mutation | standard | Cancel active run |
resolveIssue |
mutation | standard | Mark issue as manually resolved |
skipIssue |
mutation | standard | Skip an issue with optional reason |
retryPRs |
mutation | standard | Re-attempt PR creation for generated fixes |
retryFixes |
mutation | LLM | Re-generate fixes for pending/failed issues |
resolutionStats |
query | standard | Aggregate resolved/total/skipped counts |
generateReport |
mutation | LLM | Generate styled report from run data |
UI: IssueCard Component
src/components/auto-fix/issue-card.tsx renders each issue as an expandable card with:
- Header: severity badge, category badge, fix status badge, title, file path with line range
- Expanded body: detail text, evidence code blocks,
PatchViewerfor the unified diff, PR link, action buttons - Actions: Generate Fix (retry), Resolve (manual), Skip -- each with loading states
