12k
All articles

Reviewing AI-Generated Code with Difftastic

Use Difftastic to review AI-generated code with structural diffs, install it in git, and isolate behavior changes from reformatting noise.

OpenReplay Team
OpenReplay Team
Reviewing AI-Generated Code with Difftastic

AI coding tools routinely bundle a real logic change with wholesale reformatting in a single commit, and a line-based git diff drowns the one behavior-changing line in a screen of red and green. Difftastic is a structural diff tool that parses code into syntax trees and compares the trees instead of the text, so pure reformatting, re-wrapping, and whitespace churn register as no change — leaving only the edits that alter behavior. This article shows what difftastic is, how to wire it into git, and a concrete workflow for reviewing the commits an agent hands you.

Key Takeaways

  • Difftastic diffs the abstract syntax tree via tree-sitter, so reformatting and line-wrapping show as no change while a single new argument still shows as a change.
  • Difftastic supports over 30 languages and falls back to a line-oriented, word-highlighted diff for unrecognized extensions or files with parse errors.
  • Wire it in with [diff] external = difft in ~/.gitconfig, then read an agent’s commit with git show <commit> --ext-diff. The recommended git configuration changed in difftastic 0.61 (October 2024), so copy it fresh from the manual.
  • Difftastic is an external diff tool, not a git pager, so pair it with delta for everyday viewing and reach for difftastic on reformatting-heavy AI diffs.
  • difft --check-only --exit-code gates a commit on the question “did this change behavior, or just reformatting?”

The problem: AI diffs bury the line that matters

Agents and assistants like Cursor, GitHub Copilot, and Claude Code frequently reformat a whole function — or re-wrap a long call across several lines — in the same commit that changes one condition. A line-oriented diff has no way to tell the difference: it marks every touched line as changed. You get a wall of red and green, and the one edit that alters runtime behavior is somewhere inside it. Reviewing AI-generated code is exactly the situation where signal-to-noise matters most, because the reformatting is machine-generated churn and the behavior change is what you’re accountable for merging.

What is difftastic?

Difftastic is a structural diff that compares files based on their syntax rather than their text. It parses each version with tree-sitter, builds an abstract syntax tree, and diffs the trees. The practical consequence: if you reformat code so it now spans multiple lines, difftastic shows only what actually changed, and a moved or renamed block is reported as a move rather than a delete-plus-add.

Difftastic is written in Rust, created by Wilfred Hughes, and open source under the MIT license. It supports over 30 programming languages through tree-sitter parsers — not the “50+” some reviews claim. When a file has an unrecognized extension, it degrades gracefully to a line-oriented diff with word highlighting, so nothing breaks on languages it doesn’t parse.

Install difftastic and wire it into git

Install the difft binary via a package manager or cargo, then register it as git’s external diff:

# macOS
brew install difftastic

# Rust toolchain (needs Rust 1.85+ as of 0.69)
cargo install difftastic

For a one-off, pass the config inline so you don’t change anything permanently:

git -c diff.external=difft diff
git -c diff.external=difft show --ext-diff

To make difftastic your default, add this to ~/.gitconfig, straight from the manual:

[diff]
    external = difft

This changes git diff to use difftastic; other subcommands then only need the --ext-diff flag, e.g. git show --ext-diff and git log -p --ext-diff. The recommended configuration changed in difftastic 0.61 (October 2024), so if you’re copying an older blog snippet, replace it with the current manual version. Prefer inline output over the two-column default with --display inline:

difft old.js new.js --display inline

Why does difftastic win for AI-code review?

Structural diffing focuses on changed syntax nodes and eliminates the noise from non-essential edits. The clearest demonstration is the reformat-plus-one-argument case. Suppose an agent takes a one-line function signature and, because it now exceeds your line-length limit, a formatter wraps it across multiple lines — and the agent adds one argument:

// before
function createUser(name, email) {}

// after
function createUser(
  name,
  email,
  role,
) {}

A line diff highlights the entire block, because every line changed textually. Difftastic highlights only the new role argument — the single edit that changes behavior. That is precisely the kind of subtle addition an agent can slip past a reviewer who is skimming a noisy diff. The same logic applies to extractions: when an agent pulls a block into a new helper, difftastic flags the new function name as added instead of showing the wrapper and the body as one tangled delete-plus-add.

A commit-review workflow for agent output

To read only the semantic changes an agent produced, target its exact commit:

git show <agent-commit> --ext-diff
# or review the whole branch
git log -p --ext-diff main..agent-branch

Scan for three things: additions and removals difftastic actually flags as changes; blocks reported as moves or renames rather than rewrites; and any hunk that reverts to a plain line diff — that’s the signal difftastic hit an unrecognized extension or a parse error. By default, difftastic falls back to a line-oriented diff whenever parse errors are encountered, a conservative choice so it never claims two syntactically different files are the same. That matters for AI output mid-refactor, which may not parse cleanly; raise the tolerance with export DFT_PARSE_ERROR_LIMIT=20 if a nearly-valid file falls back unnecessarily.

For automation, difftastic can check whether two files share the same AST without rendering a diff:

difft --check-only --exit-code before.js after.js

This sets the exit code to 0 when there are no syntactic changes and 1 when there are — a fast gate for a pre-merge hook or CI step that answers “did the agent change behavior, or just reformat?”

Limits, and pairing difftastic with delta

Be honest about the trade-offs. Difftastic scales relatively poorly on files with a large number of changes and can use a lot of memory, so treat it as a review tool for specific commits, not a wholesale replacement for git diff. Its side-by-side display is usually good but can occasionally confuse. It’s an external diff tool, not a git pager, and it does not render inside the GitHub PR Files tab or most IDE diff panes — it’s a local CLI.

The practical answer is to run both. Keep a fast text pager for everyday viewing and switch to difftastic for the gnarly, reformatting-heavy diffs.

ToolDiff typeWorks as pager?Best for
difftasticStructural (AST)No (external diff only)Reformatting-heavy AI diffs
deltaText, syntax-highlightedYesEveryday diffs
diff-so-fancyText, prettifiedYesLightweight cleanup

Delta and diff-so-fancy are text-based; neither does AST diffing. Use delta as your default pager, and reach for difftastic when the diff is drowning in formatting churn.

A clean structural diff is a pre-merge filter: it catches the behavior-changing line before you approve. The changes that still slip through — the subtle logic edit a noisy AI diff hid — are the ones you end up reconstructing after they ship, and watching a session replay of the broken interaction is one of the more direct ways to surface that class of regression. Configure [diff] external = difft today, then review your next agent commit with git show <commit> --ext-diff and read only what changed.

FAQs

What is the difference between difftastic and delta?

Difftastic is a structural diff that parses code with tree-sitter and compares abstract syntax trees, so reformatting and line-wrapping register as no change. Delta is a text-based, syntax-highlighted pager that still diffs lines. Difftastic cannot act as a git pager and only works as an external diff tool, while delta works as a pager for everyday viewing. Use delta by default and difftastic for reformatting-heavy AI diffs.

What happens when difftastic encounters a file it cannot parse?

Difftastic falls back to a line-oriented diff with word highlighting in two cases: when a file has an unrecognized extension, and when it hits parse errors. The parse-error fallback is a deliberate, conservative choice so difftastic never claims that two syntactically different files are the same. This matters for AI output mid-refactor that may not parse cleanly. Raise the tolerance with the DFT_PARSE_ERROR_LIMIT environment variable when a nearly-valid file falls back unnecessarily.

Can difftastic show diffs inside the GitHub PR Files tab?

No. Difftastic is a local command-line tool that works as git's external diff tool, not as a rendering layer inside the GitHub PR Files tab or most IDE diff panes. You run it through commands like git show with the --ext-diff flag or git log -p --ext-diff. Third-party integrations exist for some tools like magit, but PR-tab and IDE support is limited, so difftastic is best treated as a terminal-based review step.

How do I check whether an AI commit changed behavior or just reformatted code?

Run difft with the --check-only and --exit-code flags on the two versions of a file. Difftastic checks whether the files share the same abstract syntax tree without rendering a diff, setting the exit code to 0 when there are no syntactic changes and 1 when there are. This is much faster than a full diff and makes a clean gate for a pre-merge hook or CI step that distinguishes real behavior changes from pure reformatting.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.