Never verify a result with the tool that produced it

When a result looks wrong, the instinct is to look harder through the instrument that produced it. It is the one instrument that cannot clear itself.

Originally drafted in December 2025 as part of a longer best-practices guide; revised in August 2026: split out as a standalone article, with its examples generalized beyond their original publishing-workflow setting.

An AI assistant writes an export module. The output is wrong. You ask the assistant whether its code is correct, and it assures you it is. So you add logging inside the module and watch the wrong answer assemble itself in greater detail. Both moves feel like verification. Neither is. Both inspect the result through the mechanism that produced it.

The rule I now apply across every AI-assisted project: verify through a different mechanism than the one that produced the result, and in any multi-stage flow, verify at each stage rather than only at the end. I think of it as orthogonal verification: checks built on independent axes, sharing no components with the thing they check. Ignore the first half and you confirm only that the bug is consistent. Ignore the second half and a failure anywhere reads as a failure everywhere.

Verification discipline has always mattered in software. AI assistance raises the stakes, because an assistant produces plausible code faster than anyone can read it deeply, defends that code fluently when questioned, and fails with full confidence. In synthesis coding, the human-AI collaboration discipline this series describes, trustworthy checking is the scarce resource. That makes the design of your checks an architectural concern.

Verify through a different mechanism

The cleanest illustration I have is a parsing bug. A content pipeline of mine had an export module that used custom code to parse front matter, the metadata block at the top of a Markdown file. Nested arrays were coming out mangled: tags that should have read ["tag1", "tag2"] arrived as malformed strings. My first instinct was the natural one: add logging inside the export module and trace what it was doing.

The instinct was wrong. The module was the suspect. Instrumenting it would only show me the incorrect behavior in higher resolution, narrated by the code under suspicion. What settled the question was a single move: run the same input through an independent parser and compare.

// Don't debug the suspect parser from inside.
// Parse the same input with an independent library and diff.
const matter = require('gray-matter');
console.log(JSON.stringify(matter(fileContent).data, null, 2));
console.log(JSON.stringify(exporter.parseFrontMatter(fileContent), null, 2));

gray-matter, a widely used open-source parser, handled the nested arrays correctly. My custom code did not. One comparison, no ambiguity: the bug lived in the parser, not in the source data. The lesson is independence, not library quality: the second parser owed nothing to my code, so its agreement would have cleared the parser and its disagreement convicted it. Either way I learn something, which is precisely what re-running the suspect code cannot offer.

The same move generalizes. A fix is not verified when the failing test finally passes, because the test and the fix were often written against the same understanding of the problem; confidence comes from a check that shares no code with either. Inspect the output file, then also query the system that received the data. Look at the UI, then also read the row in the database. Each method has blind spots. Two orthogonal methods rarely share them, and the region where their coverage overlaps is where justified confidence lives.

This is also my answer to a question that surfaces wherever review capacity is the bottleneck: how do you check work an AI produced faster than you can read it? Asking the same model to confirm its own output settles nothing, because its confirmation is generated by the same process that generated the bug. The principle scales from a debugging move up to an architectural decision. In ownwords, an open-source content-syncing tool I built, the verification module deliberately uses different algorithms than the conversion module it checks; the ownwords case study walks through that decision. A verifier that reused the converter’s library would inherit the converter’s bugs and certify them as correct.

Verify at each stage, not only at the end

Orthogonality says how to check. The second half of the principle says where. AI-assisted systems spend much of their lives in multi-stage flows: fetch from an API, normalize, store, transform, publish. When the value at the destination is wrong and your only check sits at the destination, everything upstream is equally suspect. The end-to-end check compresses every possible failure into a single bit: something, somewhere.

The failures themselves have a signature: a value that is correct at the source arrives wrong at the destination. The classic forms: a date that sheds its time component, an encoding that degrades into garbled characters, an identifier that quietly stops matching its record. I have chased the date failure myself; the search was the expensive part, because nothing along the flow recorded where the value went bad.

Staged verification is the cure. Probe the value at each boundary, using the cheapest tool that can see it:

# Source of truth: what does the upstream API hold?
curl -s "$API/records/123" | jq '.updated_at'

# After the fetch stage: what landed in the local file?
grep '^updated_at:' content/record-123.md

# After the publish stage: what does the destination hold?
curl -s "$DEST/records/123" | jq '.updated_at'

The first probe that disagrees with its upstream neighbor brackets the bug between two named stages. That converts “broken somewhere” into “broken between fetch and store,” and the second is a far smaller problem.

Staged checks pair with written interface contracts, which I covered in data format contracts for AI pipelines. Contracts prevent much of the drift between stages. Staged verification localizes whatever drifts anyway.

The ten-second check

The cheapest verification of all happens before anything runs. Before an operation that creates, modifies, or deletes, check the state that already exists. About to generate files into a content directory? List what the directory holds first; the existing layout announces the convention new files must follow.

My working rule: if checking costs less than ten seconds, always check. The asymmetry is lopsided enough that no judgment is required. Verification takes seconds; the mistakes it prevents take hours, and the worst of them propagate before anyone notices.

The habit matters double with an AI in the loop, because assistants act on assumptions with the same confidence they act on facts. Asked to place files, an assistant that has not looked will guess a layout, and a wrong guess means cleanup across everything it touched. So make checking the default. In the project’s instruction file (CLAUDE.md, for Claude Code) I state it as standing policy: any file operation starts by inspecting existing structure, and when the structure is unclear, ask before proceeding.

Read the dry run like a reviewer

Serious tools offer a preview for anything destructive: the dry run before the batch edit, the migration plan before the migration. It is tempting to treat the existence of the preview as the safeguard. A dry run is only a report; the safeguard is a human reading it.

Preview output is easy to glance at and approve, especially once a tool has been right many times in a row. The discipline that keeps the step honest is to decide what the output should say before looking at it. If the operation is an update to an existing record, the preview should say update, and the expected count of new writes is zero. A preview that says create, or shows writes you did not expect, is the system telling you that your model of its state is wrong, at the last moment when being wrong is still free.

In synthesis coding the working loop is: the AI proposes, the human reviews, the AI executes. For destructive operations, the dry run is the review step in artifact form. Skip the reading and the loop quietly collapses into unsupervised execution, which is the failure mode this whole discipline exists to prevent.

Why independent layers compound

The strongest check is one the design makes unnecessary. Naming conventions that make collisions impossible, and tools that detect an existing resource and switch to update mode, beat warnings and flags the caller must remember. When a failure mode can be designed away, design it away, and spend the verification budget on what remains.

What remains is covered by layers, and the layers multiply rather than add. The ten-second check catches wrong assumptions before they act. The dry run catches wrong plans before they execute. Staged probes catch bad hand-offs close to where they happen. An orthogonal verifier catches wrong results that everything upstream missed. No single layer is close to airtight. A failure that reaches production has to pass through all of them, and independent nets with different weaves rarely share holes.

That compounding is what systematic quality standards, the second pillar of synthesis coding, look like in daily practice. A standard is an intention until a check enforces it, and a check is a hazard when it shares blind spots with the thing it checks. A bug that is consistent looks exactly like correctness from inside the mechanism that produced it. The only place to see the difference is from outside.

Also published on synthesiscoding.org