← Field Notes
Field Note · misconfiguration

Your tool isn't slow. It's misconfigured.

PDF.js ran on the main thread for two months because one helpful-looking line disabled the very thing it looked like it enabled. Here is the commit.

Builds production software, and cleans up the AI-generated code that breaks it. pdflokal, his open-source PDF toolkit, is one of the repos these notes come from.

PDF.js can do its parsing on a background Web Worker, so the page stays responsive while a document renders. pdflokal, our own PDF editor, was set up to do exactly that. The worker file was configured, the setting pointed at it, and the app ran. Nothing errored.

It was also, quietly, running everything on the main thread.

The symptom was the kind you shrug off: scroll janked while a PDF rendered. PDFs are heavy, everyone knows that. So the slowness got filed under “PDFs are heavy” and left alone, which is exactly how a misconfiguration survives. It never asks to be looked at.

The console already knew

When we finally looked, PDF.js was saying it out loud.

Setting up fake worker.PDF.js has two modes: a real Web Worker, off the main thread, and a “fake worker” that runs on the main thread and blocks the UI while it parses. Ours was fake, and had been for two months.

The setting that picks the worker file was correct. But there was a second line, a preload tag in the HTML: <script src="js/vendor/pdf.worker.min.js">. That tag loaded the worker code into the global scope before PDF.js initialized. PDF.js saw the worker code already sitting there, decided a real Worker was unnecessary, and fell back to the fake one. The extra line did not add a safety net. It was the hole.

The line was added on purpose

Here is the part that makes this a pattern and not an accident. That tag was not careless. It was added deliberately, in a commit titled c5d20f7, “Fix PDF.js offline worker loading.” Someone was trying to make offline mode more robust and added the worker as a fallback. The fallback silently disabled the thing it was backing up. Good intentions, plausible code, a comment above it explaining why it was there, and a two-month regression underneath.

The fix (875f221) was to delete the line. The setting alone loads the real worker, and PDF.js already has a built-in fallback to the fake one for the case that actually matters: when creating a Worker genuinely fails, offline or blocked by a security policy. The hand-rolled fallback was solving a problem the library had already solved, and breaking a feature to do it. The diff is mostly minus signs.

What this actually teaches

This is the misconfiguration shape, and it is everywhere in AI-built code. The setup looks configured. It has the right names in the right places: the worker file, the setting, a fallback with a reasonable comment. It runs. And it runs is a fact about the process starting, not about it doing the thing you wanted.

The failure never errored. There was no crash to catch and no red test, only a UI that was slower than it should have been, for two months, in a way that was easy to blame on the file format instead of the config. The expensive bugs are the ones that do not error, because nothing sends you to look.

So when something is mysteriously slow but not broken, stop optimizing and start reading the setup. The console has usually already said it. And notice which way the fix goes: the most expensive misconfigurations are the ones that succeeded, at the wrong thing, so the fix is often a deletion. The confident extra line that looked like help.

That confidence is the tell. AI writes setup code that reads as expert, comments and all. The comment on that tag explained exactly why it was there, and was exactly wrong. Fluent, plausible, and pointed in the wrong direction. Fluency is not correctness, and nowhere does the gap hide better than in the code that just configures things and never throws.

The receipts, public
  • Where the tag was added, in a commit titled “Fix PDF.js offline worker loading” (2026-01-18)c5d20f7
  • The fix: delete the tag, and the real Web Worker comes back (2026-03-16)875f221

Why is PDF.js slow or janky when rendering?

Often it is not PDF.js itself; it is running on a fake worker. If a <script src="pdf.worker...js"> tag loads the worker code into the global scope, PDF.js detects it and skips creating a real Web Worker, falling back to the main thread, which blocks the UI during parsing. Check the console for "Setting up fake worker." Remove the script tag; the workerSrc setting alone loads the real worker.

Why does AI-generated setup code run but run wrong?

Because "it runs" and "it runs correctly" are different facts, and only the first one errors. Configuration that looks right and quietly defeats itself produces no crash and no failed test, so nobody looks. The expensive bugs are the ones that do not error.

What is a PDF.js fake worker?

PDF.js can run its parsing on a background Web Worker, or fall back to a "fake worker" that runs on the main thread. The fake worker blocks the UI during parsing, which shows up as scroll jank. PDF.js logs "Setting up fake worker" to the console when it happens.