Media: fix PDF text extraction by setting standardFontDataUrl#62175
Media: fix PDF text extraction by setting standardFontDataUrl#62175solomonneas wants to merge 9 commits into
Conversation
Greptile SummaryWires up Confidence Score: 5/5Safe to merge — single-file change with correct trailing-slash handling, graceful fallback on resolution failure, and no new dependencies or public API surface changes. The fix is minimal and correct: path.join preserves the trailing slash so pathToFileURL produces a proper directory URL (file:///…/standard_fonts/), the module-level cache prevents repeated require.resolve overhead on every PDF, and the catch block restores prior behavior on failure. No P0 or P1 issues found. No files require special attention. Reviews (1): Last reviewed commit: "Media: fix PDF text extraction by settin..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18d98711e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const require = createRequire(import.meta.url); | ||
| const pkgPath = require.resolve("pdfjs-dist/package.json"); | ||
| const fontDir = join(dirname(pkgPath), "standard_fonts/"); | ||
| standardFontDataUrlCache = pathToFileURL(fontDir).href; |
There was a problem hiding this comment.
Use a filesystem path for standard fonts in Node
In Node (the runtime used here), pdf.js v5.6.205 routes font loading through NodeBinaryDataFactory, which calls fs.promises.readFile(url) on the concatenated standardFontDataUrl + filename; this expects a filesystem path string, not a file:// URL string. Converting standard_fonts/ with pathToFileURL(...).href therefore produces paths like file:///.../FoxitSans.pfb that readFile cannot open (ENOENT), so standard-font PDFs will still fail extraction despite this change.
Useful? React with 👍 / 👎.
Update — verified end-to-end in a clean build sandboxFollowed up on the "did not run pnpm test/build locally" note in the original PR body. Built and tested the branch in a clean Ubuntu 24.04 LXC (Node v22.22.2, pnpm 10.33.0, fresh clone, no host pollution) and verified the fix end-to-end. Two follow-up commits were needed; both are now on the branch. Follow-up commits
Verification on the final commit (
|
Update 2 — second runtime bug caught + fixture test addedTwo more commits on the branch. Sandbox testing turned up a real runtime bug in the original fix that no static check would have caught, and the new fixture test now locks in the regression so it cannot return. Follow-up commits
Verification on the final commit (
|
|
CI note: the current red checks on this PR are unrelated to the PDF change. I pulled the failed run for commit
This PR only touches I also checked the current |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1c0d30751f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| text.includes("BaseException") || | ||
| text.includes("pdf.worker") || | ||
| text.includes("pdfjs-dist") |
There was a problem hiding this comment.
Narrow unhandled-rejection suppression to known pdf.js case
This matcher treats any unhandled rejection containing BaseException, pdf.worker, or pdfjs-dist as handled. Since the handler is registered at module load, once src/media/pdf-extract.ts is imported it globally suppresses those rejections and bypasses the gateway's normal fatal-unhandled-rejection path, even when the error is unrelated to the specific benign worker-init issue. That can hide real production failures and should be constrained to a precise, known-safe signature.
Useful? React with 👍 / 👎.
| } catch (pdfErr) { | ||
| logWarn(`media: PDF extraction failed for ${filename}: ${String(pdfErr)}`); | ||
| return { filename, text: "" }; |
There was a problem hiding this comment.
Surface PDF extraction failures instead of dropping file content
Catching every extractPdfContent failure here and returning an empty text payload turns broken/encrypted/malformed PDF processing into silent success. The downstream request path only attaches file context when text or images exist, so this branch now drops the uploaded file from model context without telling the caller, whereas previous behavior surfaced a request error. This makes attachment failures hard to detect and debug.
Useful? React with 👍 / 👎.
…nt-data-url # Conflicts: # src/media/pdf-extract.ts
Update — rebased on main (2026-04-20)Merged
Verified in a clean Ubuntu 24.04 LXC: Happy to squash to a single commit on landing, as originally offered. |
|
Closing as stale. PDF extraction was refactored into |
Summary
extractPdfContentcalls pdf.jsgetDocument()withoutstandardFontDataUrl, so any PDF that references the 14 standard PDF fonts (Helvetica, Times, Courier, etc.) emitsUnknownErrorException: Ensure that the standardFontDataUrl API parameter is providedand yields empty/garbled extracted text.{"detail":"Instructions are required"}), which can also trigger an unnecessary failover/timeout cascade.pdfjs-dist/standard_fonts/directory viacreateRequire+require.resolve("pdfjs-dist/package.json"), convert it to afile://URL, and pass it asstandardFontDataUrltogetDocument(). The result is cached. If resolution somehow fails the call falls back to the previous behavior (undefined) instead of throwing.extractPdfContentsignature, or any other media pipeline file.Change Type (select all)
Scope (select all touched areas)
(touches
src/media/pdf-extract.tsonly — the PDF text extraction helper used by media tools)Linked Issue/PR
Root Cause (if applicable)
getDocument()was called with{ data, disableWorker: true }only. pdf.js requiresstandardFontDataUrlto load font data for the 14 standard fonts; without it, parsing throws on any PDF that embeds references to those fonts (which is most of them). The font files already ship insidepdfjs-dist/standard_fonts/— they were just never wired up.extractPdfContentagainst a real PDF that references standard fonts. A fixture-based unit test would have caught this on first introduction.getTextContent()for text-only extraction so the failure looks like a generic warning in logs rather than an actionable error.Regression Test Plan (if applicable)
src/media/pdf-extract.test.ts(new) with a small fixture PDF undersrc/media/__fixtures__/standard-fonts.pdf.extractPdfContent({ buffer, ... })on a PDF that references Helvetica/Times must return non-emptytextand emit nostandardFontDataUrlwarnings.User-visible / Behavior Changes
PDFs that previously returned empty text (and triggered downstream model errors) will now extract text successfully. No config changes; no defaults changed; no new flags.
Diagram (if applicable)
Security Impact (required)
NoNoNo—standardFontDataUrlis afile://URL pointing insidenode_modules/pdfjs-dist/standard_fonts/. No network fetch is introduced.NoNo— the resolved path is constrained to the bundledpdfjs-distpackage directory.Repro + Verification
Environment
2026.4.6-beta.1global install (npm i -g openclaw@latest), gateway running under systemd user serviceagents.defaults.pdfModel, defaultpdfMaxPages, no overridesSteps
journalctl --user -u openclaw-gateway -fwhile the bot processes it.Expected
Actual (before this fix)
Evidence
Before (gateway log on a 14-page syllabus PDF):
After (manual extraction against the same PDF using a patched build of
dist/pdf-extract-*.jswith the equivalent option set):No
standardFontDataUrlwarnings.Human Verification (required)
dist/pdf-extract-*.json my running gateway to setstandardFontDataUrlagainstnode_modules/pdfjs-dist/standard_fonts/and re-extracted the same syllabus PDF that originally failed. Got 4330 characters of clean text from page 1, no font errors.node_modules/pdfjs-dist/standard_fonts/(FoxitSerif.pfb, FoxitSans, LiberationSans-Regular.ttf, etc.) so the resolution target is real and shipped with the dependency.createRequire(import.meta.url).resolve("pdfjs-dist/package.json")resolves correctly under Node 22 ESM in this repo's module layout.pdfjs-distnot installed:getStandardFontDataUrl()catches therequire.resolvefailure, caches the negative result, and returnsundefined.getDocument()then behaves exactly like before this PR (no regression).require.resolveon every PDF.pnpm test/pnpm buildlocally for this PR (no local TS toolchain set up on this box). CI will validate. If maintainers want me to add the fixture-based unit test described above before landing, happy to do that in this PR.Review Conversations
Compatibility / Migration
YesNoNoRisks and Mitigations
require.resolve("pdfjs-dist/package.json")fails for some unusual install layout (e.g. an unconventional bundler that strips package.json).try/catch. On failure we cache the negative result and returnundefined, which restores the pre-PR behavior. No new failure mode is introduced.standard_fonts/directory.try/catchfalls back gracefully and the bug returns to its current state (no worse).