Skip to main content

Troubleshooting

Most inputs diff quickly, but some are slow, and some edge cases produce results that need explanation. Each issue below lists the symptom, the cause, and a workaround.

Diff is too slow

Symptom: Diffing a large tree or monorepo takes much longer than expected. Cause: Usually parsing, not matching. Tree matching is linear in node count (350k nodes in roughly 70 ms), and container matching at the bottom of the tree is O(n²) only in degenerate worst cases. The first parse of a huge file on a cold grammar cache dominates. Workaround: Diff specific files instead of whole directories. On a large repository, diffing one file is much faster than diffing a directory or the whole working tree.
There is no node-count cap: every tree size is matched for real. If a pathological input is slow, it is slow on the parser side, not because matching gave up.

Deep nesting

The matcher walks trees with explicit stacks, not recursion. A 200,000-level-deep chain matches in about 100 ms with no stack overflow, and deeply nested JSON, HTML, and TypeScript all diff normally. If a runtime still crashes on some pathological nesting, the recursion is in a parser or the runtime itself (for example JSON.parse). Open an issue with the input that broke.

YAML diffs are wrong

Symptom: YAML diffs lose nesting, report spurious changes, or show flat structures that should be nested. Cause: The YAML parser requires 2-space indentation. YAML formatted with 4-space indentation (or tabs) can lose its nesting structure during parsing. Workaround: Reformat YAML to 2-space indentation before diffing.
YAML with non-standard indentation (4 spaces, tabs) may parse with collapsed or shifted nesting, producing misleading diffs. Convert to 2-space indentation for correct results.

Renames show as delete + insert

Symptom: A renamed function or variable appears as a pair of Delete and Insert actions instead of a rename. Cause: Rename detection at the leaf level is a known limitation. Individual leaf nodes do not carry enough context for the matcher to pair them as renames. Workaround: This is largely cosmetic. The narration layer (@ossl-dev/differens-narrate) reads the surrounding context and phrases such changes as moves or renames in its prose output. That is the intended way to consume diffs, whether by humans or LLMs.

Markup parser misbehaves

Symptom: HTML/XML diffs are wrong, especially around attributes. Cause: The markup tokenizer (T3 tier) is regex-based, and it can misparse a > that appears inside an attribute value, for example <div title="a > b">. The tokenizer treats the > as closing the tag. Workaround: Rewrite the attribute value so it does not contain a raw > (for example, use &gt; or &#62;), or diff the file with a different tier.
Attribute values containing a bare > can confuse the markup tokenizer, producing truncated or malformed trees. Escape them as &gt; to be safe.

Build errors

Symptom: bun run build fails with missing module or resolve errors. Cause: Dependencies were never installed, or the installed Bun version is too old. Differens requires Bun >= 1.3. Workaround:
If you upgraded Bun, run bun install again to refresh the lockfile and native bindings.

TypeScript errors

Symptom: Editor or CI reports type errors across packages. Cause: Stale build artifacts, or a change that broke a shared type across the monorepo. Workaround: Run the typechecker across all packages to see the full picture:
If errors persist after a clean typecheck, the packages depend on each other through the workspace. Rebuild the dependents:

Still stuck?

Open a GitHub issue at ossl-dev/differens with the command you ran, the input shape (file, directory, or commit range), and the output you saw. Known limitations are tracked in ROADMAP.md at the repo root.