This guide covers LLM output validation with production-focused examples and trade-offs.
An LLM that emits a patch which does not parse is not “almost right” — it is a failed tool call. Treat syntax, typecheck, and import resolution as hard validators in front of the working tree. Soft prompts (“please emit valid TypeScript”) are not a substitute for a gate that refuses to write bytes.
⚡ TL;DR: Parse every proposed file with the real language toolchain before git apply. Fail closed on parse errors, unresolved imports, and typecheck deltas outside an allowlist. Retry with the validator error as structured feedback at most N times, then escalate to a human. Aligns with Structured Outputs for Codegen and Cursor Agent Mode: Multi-File Refactors That Survive CI Gates.
Validator pipeline before any write — LLM output validation
// validate-patch.ts
import ts from "typescript";
import { applyPatch } from "./diff";
import { execa } from "execa";
export type Validation = { ok: true } | { ok: false; stage: string; detail: string };
export async function validatePatch(opts: {
repoRoot: string;
unifiedDiff: string;
maxTypeErrors?: number;
}): Promise<Validation> {
const tree = applyPatch(opts.repoRoot, opts.unifiedDiff, { dryRun: true });
if (!tree.ok) return { ok: false, stage: "apply", detail: tree.error };
for (const file of tree.files) {
if (!/\.(ts|tsx|js|jsx)$/.test(file.path)) continue;
const sf = ts.createSourceFile(file.path, file.content, ts.ScriptTarget.Latest, true);
const parseDiags = (sf as any).parseDiagnostics ?? [];
// ✅ Real parse tree — not a regex "looks like code"
if (parseDiags.length) {
return {
ok: false,
stage: "parse",
detail: parseDiags.map((d: ts.Diagnostic) => ts.flattenDiagnosticMessageText(d.messageText, "\n")).join("\n"),
};
}
}
// Materialize to a temp worktree for typecheck
const { exitCode, stderr } = await execa(
"pnpm",
["exec", "tsc", "-p", "tsconfig.json", "--pretty", "false", "--noEmit"],
{ cwd: tree.tempRoot, reject: false },
);
if (exitCode !== 0) {
const errors = stderr.split("\n").filter((l) => /error TS/.test(l));
if (errors.length > (opts.maxTypeErrors ?? 0)) {
return { ok: false, stage: "typecheck", detail: errors.slice(0, 40).join("\n") };
}
}
return { ok: true };
}
✅ Dry-run apply → parse → typecheck → only then write.
Structured retry with validator feedback
async function generateUntilValid(agent: Agent, prompt: string, budget = 3) {
let feedback = "";
for (let i = 0; i < budget; i++) {
const diff = await agent.proposePatch(prompt + feedback);
const v = await validatePatch({ repoRoot: process.cwd(), unifiedDiff: diff });
if (v.ok) return diff;
// ✅ Feed machine-readable failure back — not "try again"
feedback = `\n\nPREVIOUS_PATCH_REJECTED stage=${v.stage}\n${v.detail}\nEmit a corrected unified diff only.`;
}
throw new Error("validator_budget_exhausted");
}
Import resolution and forbidden paths
const FORBIDDEN = [/^pnpm-lock\.yaml$/, /\/generated\//, /^\.env/];
function assertImportsResolve(file: string, content: string, resolver: Resolver) {
for (const spec of extractImportSpecifiers(content)) {
const resolved = resolver.resolve(file, spec);
if (!resolved) throw new Error(`unresolved_import:${spec}`);
}
}
function assertNotForbidden(paths: string[]) {
for (const p of paths) {
if (FORBIDDEN.some((re) => re.test(p))) throw new Error(`forbidden_path:${p}`);
}
}
What “valid” means per language
| Language | Parse | Types / lint | Extra |
|---|---|---|---|
| TypeScript | ts.createSourceFile |
tsc --noEmit |
import resolver |
| Python | ast.parse |
ruff check / pyright |
— |
| Go | go/parser |
go test ./... compile |
— |
| Terraform | hclparse |
terraform validate |
plan gate |
terraform validate locally before any apply.
Closing checklist
- [ ] No patch enters the working tree without parse success
- [ ] Typecheck / compile runs in a temp worktree; baseline errors allowlisted
- [ ] Unresolved imports and forbidden paths fail closed
- [ ] Retry loop caps at N; validator stderr becomes next prompt context
- [ ] Metrics: reject rate by stage (parse / typecheck / import)
- [ ] Semantic review still runs after validators — see Semantic Diff Review
Related reading
- Structured Outputs for Codegen: JSON Schemas That Actually Compile
- Cursor Agent Mode: Multi-File Refactors That Survive CI Gates
- Semantic Diff Review: AI That Flags Behavioral Drift Only
- Claude Code Hooks: Gate Risky Shell Commands Before CI Runs
Related: AI-Written Terraform: Plan Diff Gates Before Any Apply Runs · Prompt Engineering for Developers: The Patterns That 10x Your AI Coding Output · Day 65: PII Redaction Before Embeddings
Related: Day 14: Guardrails Before Clever Prompts · Unit Testing Best Practices: Write Tests That Actually Catch Bugs Before Production · Policy-as-Code for AI: OPA Checks Before Every Shell Execution
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Tool-Augmented RAG: Agents Fetch Exact File Ranges Before Answering - CheatCoders