Engineering Journal
Pdf Processor
Pdf Processor

Postmortem: Three Bugs That Were All the Same Mistake

2026-05-31

TLDR

During the implementation of our PDF processor observability panel, three distinct bugs surfaced: a worker thread hang during page message posting, phantom horizontal dividers inside paragraphs, and silent re-extraction failures. Despite different symptoms, all three shared one root cause: new observability code running alongside existing algorithms without respecting environmental constraints (serialization limits, stage execution order, and buffer transfer semantics).
Defect IncidentObserved SymptomUnderlying Environmental ViolationStructural Resolution
Worker Thread HangExtraction stalls indefinitely on image pagesSerializing large base64 image strings in postMessageOmit base64 image data from manifest payload
Phantom DividersParagraph copy split by false horizontal rulesDivider stage executed before text regions were populatedRe-ordered pipeline stage execution
Re-Extract HangRe-extraction button hangs after initial document loadArrayBuffer neutering during sub-worker transferExecute bytes.slice() prior to transfer

Problem statement: three parallel failures

Adding an observability inspection layer to our document extractor triggered three independent defects:

  1. Extraction Hang: Processing PDF pages containing large images froze the UI progress bar.
  2. Document Corruption: Extracted HTML contained spurious <hr> horizontal divider tags inside body paragraphs.
  3. Re-Extraction Freeze: Single-page re-extraction worked on initial page loads, but failed silently on subsequent re-runs.

Technical failure modes & incident analysis

Incident 1: structured clone payload bloat

Incident 2: out-of-order classifier execution

Root Cause: The divider line detector executed before* text paragraph classification. Failure Mode: The divider stage checked: "Is this horizontal vector line inside an existing text block?"* Because paragraph blocks had not been classified yet, the check evaluated to false, converting inline text underlines into document-level <hr> dividers.

Incident 3: buffer neutering via worker transfers


The lessons & architecture principles

// REFACTORED WORKER INITIALIZATION PATTERN
self.onmessage = async (e) => {
  const { bytes, options } = e.data;

// 1. Slice bytes to preserve independent copy for re-extraction const cachedBytes = bytes.slice();

// 2. Execute pipeline stages in correct dependency order const pdfDoc = await pdfjsLib.getDocument({ data: bytes }).promise; const pageRegions = await executeOrderedPipeline(pdfDoc, options);

// 3. Post lightweight manifest (No heavy base64 strings!) self.postMessage({ type: 'page_complete', regions: sanitizeManifestForTransfer(pageRegions) }); };

Rule of thumb: When adding observability or inspection layers to working pipelines, audit payload serialization sizes, stage dependency execution order, and buffer transfer lifetimes.
Read this post in the full Engineering Journal →