Fixing LaTeX errors in under a second


In the past few years, frontier AI models have made significant progress in mathematical research. Every week, a new result is announced, from the disproof of the Jacobian conjecture [1] to a finite-time singularity for the Navier–Stokes equations [2].

Yet has any of this changed how mathematicians work? For many of them, not much. In our conversations, two reasons came up again and again:

  1. Models are not integrated into the tools mathematicians use.
  2. Models are seen as substitutes for mathematicians rather than as tools in their hands.

We were surprised by how little the tools they spend the most time on have changed. Overleaf, where most of them write up their papers, was founded in 2012, and writing in it today feels much as it did then.

"Undefined control sequence"

When we interviewed mathematicians, one of the most common annoyances we heard about in their day to day work is battling with LaTeX compilation errors. LaTeX is the bread and butter of mathematics publishing, and fluency in its long tail of packages takes years.

Until it is reached, writing is constantly interrupted by those errors, which makes it hard to think while writing. This forces many researchers to write on other surfaces, then spend time typing their work in LaTeX.

In other words: every missing brace, every package that clashes with another is a minute of annoyance sacrificed forever on the altar of an unforgiving compiler.

Just fix it

The most obvious solution is to ask a chatbot to fix errors. That works decently well, except:

What we were looking for was different: a model that can run fast directly in the surface researchers work in, and without interrupting their work. Crucially, it has to be able to suggest changes, so they can be reverted in a click (Sundial's editor supports this natively). It also has to be very cheap to run at scale, given it will run on every compilation error.

Breaking LaTeX on purpose

We chose Thinking Machines' Inkling-Small as a starting point because it is fast, open weight and built to be fine-tuned.

Our first attempt involved creating many synthetic examples by taking valid LaTeX documents and dropping errors in them (heartbreaking, I know). From the broken document and the compilation error statement, we could train the model to emit reverse patches as search-and-replace edit blocks.

We fine-tuned the model, then tried it on examples donated by our generous beta-testers. It turned out it didn't transfer well to real examples. The model was learning our perturbations but not real LaTeX errors users were getting.

Building TeXtinguisher

Our next attempt had to be more ambitious.

We scraped 272,000 questions from TeX.StackExchange and kept them when:

  1. Their snippet still fails today with a located error.
  2. The accepted answer's version compiles.
  3. The difference replays exactly as a patch.

This gave us the largest dataset of real LaTeX errors and their fixes to date: 3,978 pairs, against 88 in the largest one previously released [3].

One thing that surprised us in the data is that 42% of the candidate documents compile fine today. This is because LaTeX is evolving and some packages have been fixed since the question was asked. So we removed those from the dataset.

Another 5,700 questions passed the first test but failed the third, because the answer could not be replayed as a patch. We kept those too: a teacher model wrote a fix for each, and we used it wherever it compiled.

A fix, whoever wrote it, is scored three ways, all deterministic: does it compile, how many characters of content it deleted, and whether its PDF matches the accepted answer's.

1\documentclass[12pt]{article}
2\usepackage{siunitx}
3\begin{document}
4\si{300}{\nanogram\per\millilitre}5\end{document}

pdflatex

Undefined control sequence.
l.4 \si{300}{\nanogram
                      \per\millilitre}

fix: the diff between the question and the accepted answer

- \si{300}{\nanogram\per\millilitre}+ \SI{300}{\nano\gram\per\milli\litre}
In siunitx, \si takes a unit on its own; a number with a unit is \SI. And \nanogram is not a unit siunitx knows, it wants \nano\gram. The accepted answer fixes both on the same line.
1\documentclass{article}
2\usepackage[symbol=$\uparrow$]{footnotebackref}
3\usepackage[hidelinks]{hyperref}4
5\begin{document}
6
7This is a statement.\footnote{And it is supported by this footnote.}
8
9\end{document}

pdflatex

LaTeX Error: Option clash for package hyperref.
See the LaTeX manual or LaTeX Companion for explanation.
l.3

fix: the diff between the question and the accepted answer

- \usepackage[symbol=$\uparrow$]{footnotebackref}- \usepackage[hidelinks]{hyperref}+ \usepackage[hidelinks]{hyperref}+ \usepackage[symbol=$\uparrow$]{footnotebackref}
Line 3 is not the problem. footnotebackref loads hyperref itself on line 2, so when line 3 loads it again with different options, LaTeX refuses. Loading hyperref first, with its options, fixes it.
1\documentclass[12pt,a4paper,oneside]{article}
2
3\begin{document}
4
5\begin{center}
6
7M = < X,Y,S,ta, \delta int, \delta ext, \lambda >8
9\end{center}
10
11\end{document}

pdflatex

Missing $ inserted.
<inserted text>
                $
l.7 M = < X,Y,S,ta, \delta

fix: the diff between the question and the accepted answer

- \begin{center}- M = < X,Y,S,ta, \delta int, \delta ext, \lambda >- \end{center}+ \[+ M = \langle X, Y, S, ta, \delta_{\mathrm{int}}, \delta_{\mathrm{ext}}, \lambda \rangle+ \]
The formula is written as plain text, so \delta and \lambda are used outside math mode. The accepted answer puts the whole line in display math and, while there, writes the angle brackets and the subscripts properly. Adding dollar signs alone would have compiled too.
1\documentclass{article}
2\usepackage{xcolor}
3\usepackage{pgfplots}
4\begin{document}
5  \begin{tikzpicture}
6    \begin{axis}
7      \foreach \foridx/\forcol in {1/black,2/red,3/green,4/blue,5/orange}{
8            \addplot[\forcol]{x^\foridx};
9      }
10    \end{axis}11  \end{tikzpicture}
12\end{document}

pdflatex

Undefined control sequence.
\pgfkeyscurrentkey ->\forcol

l.10     \end{axis}

fix: the diff between the question and the accepted answer

-       \foreach \foridx/\forcol in {1/black,2/red,3/green,4/blue,5/orange}{-             \addplot[\forcol]{x^\foridx};+       \pgfplotsforeachungrouped \foridx/\forcol in {1/black,2/red,3/green,4/blue,5/orange}{+             \expandafter\addplot\expandafter[\forcol]{x^\foridx};
The error is reported at \end{axis}, but the cause is the \foreach on line 7. pgfplots stores the body of an axis and runs it at \end{axis}, and by then the loop variables no longer exist. The fix is pgfplots' own loop, \pgfplotsforeachungrouped, plus an \expandafter so that \forcol is expanded before \addplot reads it.

The training run

After a few attempts, we landed on a three-step recipe.

First, we start with SFT on labeled real cases, then we add fixes written by the teacher model for the unlabeled cases.

When SFT stops showing gains, we start RL: for each broken document, the model proposes eight fixes, each scored with the same three checks, and the score is the reward. We used GRPO from the Tinker cookbook. This got us close to frontier model accuracy at a fraction of the latency.

We tried tweaking the reward function in many ways.

  1. The first one was only a compilation check. This led the model to sometimes not fix the issue, instead removing content until the document built.
  2. We tried a bonus for the smallest edit in the group, but it often rewarded small hacks that change the document.
  3. We tried having an LLM judge the fixes, but it would call most of the human fixes hacks.

In the end, what worked best was two checks: 1 point if the document compiles, 0.5 more if the compiled PDF matches the solution's (same number of pages, at least 98.5% word-sequence agreement), both shrinking as deleted content grows and reaching zero at 150 characters. A hard cutoff instead of the decay made no measurable difference.

After all this, our model still lacked something: multi-file understanding. Sometimes, errors depend on files different from the one that failed compilation. We thus reconstructed projects from GitHub, TeX.StackExchange and ArXiv where multiple files were involved. Adding those to the set and giving our model relevant context put it on par with Claude Fable 5.1 and GPT-5.5, and within six points of GPT-6 Astra at a fourteenth of its cost.

Another thing we tried is training the model in an agentic loop instead of fixing errors in one shot. It was much slower and only marginally better in our tests, so we kept a single shot.

Bar chart of real-error accuracy by training stage.
Real-error accuracy by training stage on the 287-case dev set; the final model scores 85.2% on the 647 held-out cases in the table below
Real-error accuracy by training stage on the 287-case dev set; the final model scores 85.2% on the 647 held-out cases in the table below
Real-error accuracy by training stage on the 287-case dev set; the final model scores 85.2% on the 647 held-out cases in the table below
Real-error accuracy by training stage on the 287-case dev set; the final model scores 85.2% on the 647 held-out cases in the table below

Results

On 647 real single-file errors held out after all training, measured end to end from the same client, with the same 8,000-token budget for every model:

modelfixes compileseconds
per fix (p50)
output
tokens
Inkling-Small, fine-tuned (ours)85.2%0.95162
GPT-6 Astra90.9%1.30208
Claude Fable 5.182.4%5.72812
GPT-5.580.7%2.47866
Grok 4.680.7%7.905,110
Claude Opus 579.8%4.01936
Gemini 3.8 Flash76.0%3.373,396
Gemini 3.7 Flash69.7%2.421,079
Claude Sonnet 554.3%2.42919
Inkling-Small, base47.9%4,231
Claude Haiku 4.546.7%2.91315
Scatter plot of accuracy against median latency for 20 models.
669 held-out real errors from TeX.StackExchange; median end-to-end latency, same client
Scatter plot of accuracy against cost per thousand fixes for the same models.
Accuracy against cost per 1,000 fixes, same 669 errors
    647 held-out real errors. Latency: median of 15 prompts; ours on a dedicated 8×H200 deployment. Cost: list prices × measured tokens; ours from measured throughput at 64 concurrent requests.
    647 held-out real errors. Latency: median of 15 prompts; ours on a dedicated 8×H200 deployment. Cost: list prices × measured tokens; ours from measured throughput at 64 concurrent requests.
    647 held-out real errors. Latency: median of 15 prompts; ours on a dedicated 8×H200 deployment. Cost: list prices × measured tokens; ours from measured throughput at 64 concurrent requests.

    We serve a merged checkpoint under vLLM on a single 8×H200 node on Modal. At 64 concurrent requests that one node handles about 28,500 fixes an hour, which is roughly $1.30 per thousand. This is cheap enough to fire on every failed compile rather than only when the user asks.

    Limitations

    Most of the benchmark comes from Stack Exchange questions: short documents with one error each, where we index on whatever the accepted answer did. The multi-file projects from GitHub and arXiv are closer to real work, but there are fewer of them. To make sure our model writes good fixes rather than just making the error go away, we would have to gather higher-quality labeled examples from real projects with many files.

    On a smaller set of errors where we could verify the human fix, our model more often makes the document compile by deleting some of the user's content than frontier models do. We're working on tightening the checks on the compiled PDF so the model can never hack its way to compilation.

    What we learned

    Three things we learned:

    1. Designing the right reward takes iterations. Every time we checked only that the document compiled, the model found a way to make it compile that no author would accept.
    2. Synthetic data needs careful calibration. Our synthetic examples taught the model our idea of an error instead of real errors.
    3. Frontier models catch up quickly. We had the most accurate model on this benchmark until GPT-6 Astra came out. The lasting value of fine-tuning is speed and cost, where ours is still 27% faster and 14 times cheaper.

    The benchmark, the mining scripts, the RL environment and the evaluation harness are at github.com/sundial-org/textinguisher, and the benchmark is on Hugging Face. We're rolling out the model inside the Sundial editor over the next few days. When a compile fails, the fix is applied in under a second and the PDF rebuilt, so the user can keep working without being interrupted. We apply all fixes as suggestions, so they can be reverted by the user if needed.

    References

    [1] T. Tao, "A digestion of the Jacobian conjecture counterexample", July 2026. terrytao.wordpress.com/2026/07/21/a-digestion-of-the-jacobian-conjecture-counterexample/

    [2] OpenAI, "On the Navier–Stokes Millennium Prize Problem", September 2026. openai.com/index/navier-stokes-solution

    [3] "TeXFix-Bench: An Empirically Grounded Multi-Format Benchmark for LLM-Based Document Source Repair", arXiv:2608.07617, 2026. arxiv.org/abs/2608.07617

    More from the blog