· Paul Crossland
From Header to Bytes to Blank Text: The OCR Receipt
Fresh crawler changes show why file extraction must reconcile headers, byte signatures, parser capability, empty output, and cache admission.
A browser fetches a URL ending in .jpg and receives AVIF after CDN negotiation. Another endpoint reports Content-Type: image/jp2 but sends a PDF. Both responses are valid downloads. Routing either one by its label alone sends real bytes to the wrong parser.
Even correct routing does not settle the result. OCR can honestly find no text, or a transient converter defect can incorrectly return no text. Both outcomes may be an empty Markdown string and a 200. If that string is cached forever under the file digest, a one-day pipeline failure can become the permanent answer for unchanged bytes.
The practical thesis is: file-derived extraction should reconcile declared media, byte-identified format, parser capability, semantic output, and cache eligibility as separate decisions; an empty result may complete one request without being safe to remember as a durable property of the source.
This is data-quality guidance for public or properly authorized files. MIME inspection, OCR, and parser retries do not authorize access to private documents, payment-bound content, or restricted downloads. Preserve login, denial, challenge, rate-limit, encryption, and publisher-policy outcomes instead of using format variation to route around them.
Fresh evidence from the file boundary
Several Firecrawl changes in the last two days expose one connected pipeline:
| Primary source | Date | What it contributes |
|---|---|---|
| Firecrawl pull request 4500 | 2026-09-02 | Separates image parsing from PDF options, makes image OCR part of the default parser set behind a rollout flag, and prevents cached OCR output from being served to requests that opted out or lack the capability. |
| Firecrawl pull request 4502 | 2026-09-02 | Reports a production handoff labeled image/jp2 whose bytes began as PDF, then routes unrecognized declarations by bounded PDF, ZIP-container, OLE, or raster signatures and reconciles extension-derived parser flags. |
| Firecrawl pull requests 4501 and 4510 | 2026-09-02 and 2026-09-03 | Add byte recognition and OCR paths for JPEG 2000, WebP, and AVIF. The WebP/AVIF change notes that CDN content negotiation can return those formats from URLs ending in .jpg or .png, and that downstream transcoder deployment order matters. |
| Firecrawl pull request 4506 | 2026-09-02 | Treats a verified image whose OCR finds no text as a successful empty document rather than an all-engines failure. |
| Firecrawl pull request 4515 | 2026-09-03 | Reports empty image OCR results persisting in a digest cache after a temporary no-text gate defect; the open proposal makes cached empty image results misses and declines to store new empty image results while retaining separate PDF behavior. |
Pull request 4515 was still open at publication time; merged changes are not proof that every deployment contains them.
Recent Better Fetch posts already cover download integrity, document provenance, screenshot transformations, cleaning loss, and deadline-degraded caches. Repeating “inspect the MIME type” or “do not cache bad output” would add little. The new surplus is the apparent contradiction across two current fixes: empty OCR can be valid for the current request while still being too weak for a long-lived cache. That distinction requires media identity and pipeline capability to travel with the result.
A file has several identities before it has text
At least four media claims can coexist:
- Locator claim: filename extension and URL path.
- Protocol claim: response
Content-Typeand disposition metadata. - Byte claim: a bounded signature or container identification.
- Parser claim: the format the selected decoder actually opens.
They should not be collapsed into one mime_type field. A CDN may negotiate WebP or AVIF behind a .jpg path, while a misconfigured origin may label a PDF as an image.
Byte sniffing is a routing hint, not complete validation. A ZIP header identifies a container, not necessarily an Office document; RIFF is insufficient without the WEBP tag. Keep signature checks bounded and follow them with parser-level structural validation.
When claims conflict, retain all of them. Useful verdicts include declared_and_detected_agree, declared_unknown_bytes_known, declared_mismatch, signature_ambiguous, and format_unverified.
Capability is part of routing
Recognizing a format does not prove the deployed conversion chain can process it. The current WebP and AVIF work required an upstream service to transcode those formats before its document loader could open them. Deploying API-side recognition first would route more files into a backend that still rejects them, adding latency without adding capability.
Compile a parser plan from:
- requested parser set and explicit opt-outs;
- account or tenant rollout flags;
- detected format and confidence;
- decoder, transcoder, OCR, and layout-engine versions;
- maximum bytes, pixels, pages, and processing budgets;
- supported handoff formats at every service boundary;
- fallback rules and their allowed output classes.
Return the effective plan, not just the caller's options. Cache lookup belongs after compilation: a request that excludes image OCR must not receive cached OCR merely because identical bytes were processed under another capability profile. Unknown formats should fail closed rather than entering a costly speculative parser waterfall.
Empty is an output class, not a boolean
A blank scan, photo, or tiny thumbnail can produce honest empty OCR. Failing solely because markdown.length == 0 confuses “no textual representation” with “engine failure.” Pull request 4506 separates parser completion from text presence.
But request success and reusable knowledge have different burdens of proof. Pull request 4515 documents the other side: a temporary no-text gate emptied legible images, and byte-digest caching preserved those outputs after the gate was fixed. Identical input bytes did not imply identical correct output because the conversion policy had changed.
Classify emptiness with evidence:
no_text_observed: decoder opened the verified image and OCR completed, but found no accepted text;text_below_acceptance: OCR produced candidates that failed declared confidence or quality thresholds;unsupported_format: no admitted decoder path existed;converter_rejected: the selected service rejected or could not decode the payload;pipeline_incomplete: timeout, crash, rollout mismatch, or missing stage prevented a trustworthy verdict;policy_stop: access or content policy prohibited processing;unknown_empty: an empty string survived without enough stage evidence.
A caller may accept no_text_observed. A durable cache can decline it when recomputation is cheap and a transient false negative is costly. PDFs may use a different rule when blank extraction is expensive and independently supported. Cache policy should follow artifact class and evidence strength, not string length.
Issue a media-to-text receipt
Attach one receipt to every file-derived representation:
- source URL class, redirect chain, status, declared media type, disposition filename, and extension;
- byte length, digest, signature window length, detected format, detector version, and confidence;
- conflict verdict among URL, header, bytes, and parser;
- requested and effective parser sets, rollout flags, routing rule ID, and fallback sequence;
- decoder, transcoder, OCR, and normalizer versions plus deployment generation;
- image dimensions or document page count when known, processing duration, and stage outcomes;
- raw OCR candidate count, accepted text length, confidence summary, warnings, and semantic-empty class;
- cache lookup key version, candidate provenance, eligibility comparison, hit or miss reason, write decision, and expiry policy;
- final verdict such as
text_accepted,no_text_observed_not_cached,format_conflict_resolved,capability_missing,pipeline_incomplete, orpolicy_stop.
Keep document bytes, extracted text, private URLs, and credentials out of broad telemetry. Digests, classifications, versions, counts, and restricted artifact references are usually enough to diagnose routing and cache incidents.
A fixture matrix for negotiated and mislabeled files
Run the exact production handoff, services, and cache through controlled files:
- Serve PDF, Office, JPEG 2000, WebP, and AVIF bytes under correct, generic, absent, and deliberately wrong media headers. Require deterministic routing and preserve every conflicting claim.
- Serve WebP and AVIF from
.jpgand.pngURLs under differentAcceptheaders. Bind the detected bytes and accepted text to the actual response variant, includingVaryand content-encoding evidence. - Include RIFF that is not WebP, ZIP that is not an admitted document, truncated signatures, malformed containers, and random bytes. Require bounded rejection rather than an unrestricted parser waterfall.
- Deploy detector support without transcoder support in a canary. Require
capability_missing; never convert extra backend round trips into generic source failure. - OCR an image with text, a blank scan, a photo, and a tiny thumbnail. Require distinct semantic-empty outcomes rather than one empty-success bucket.
- Force a temporary OCR gate defect to return empty for a known-text image, then fix the gate and repeat identical bytes. Verify the old result cannot remain canonical merely because its digest matches.
- Request the same bytes with image parsing enabled, explicitly omitted, and unavailable by rollout policy. Confirm cache lookup cannot broaden the effective parser plan.
- Return encrypted, denied, login-bound, rate-limited, and oversized fixtures. Preserve policy and admission outcomes; do not vary parser, identity, session, or route to seek broader access.
The operator decision rule
When a file produces missing or empty text, locate the first unsupported transition. If declarations conflict, preserve the response and route by a bounded byte detector into structural validation. If the format is known but one service lacks capability, quarantine that deployment generation instead of blaming the source. If OCR completed with no text, return a semantic empty verdict rather than a generic engine failure. Before caching, ask a stricter question: does this receipt prove that emptiness is durable enough to reuse under the same parser plan and pipeline generation?
A file digest proves the bytes stayed the same. It does not prove the header was right, the selected backend could decode them, the OCR policy was healthy, or an empty result deserves permanence. Reconcile those stages, and “blank Markdown” becomes a diagnosable outcome instead of either a false failure or an immortal false negative.