Windows: render_docx.py does not automatically find the bundled poppler — PDF rasterization fails

Open 💬 0 comments Opened Aug 25, 2026 by Jizhixing-Kieran

What version of the Codex App are you using (From “About Codex” dialog)?

26.818.8289.0

What subscription do you have?

Plus

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What issue are you seeing?

On the Windows Codex Desktop bundled-runtime layout, render_docx.py (from the documents skill) fails at the rasterization step whenever the process PATH does not already contain the bundled poppler directory:

pdf2image.exceptions.PDFInfoNotInstalledError: Unable to get page count. Is poppler installed and in PATH?

The bundled poppler binaries are present in the runtime, but nothing puts them on PATH in a form the bundled Python can execute.

Important: the failure is not in the LibreOffice step — the intermediate PDF is produced correctly, and only the PDF→PNG rasterization fails. This makes the error highly misleading. It reads as a LibreOffice or system-level problem, and that is exactly how it was first reported to me before I traced it.

Relevant to the layout, the skill tree and the runtime tree are separate:

<HOME>\.codex\plugins\cache\openai-primary-runtime\...     <- skill
<HOME>\.cache\codex-runtimes\codex-primary-runtime\...     <- runtime + dependencies

Versions:

| | |
|---|---|
| Codex App | 26.818.8289.0 (MSIX package OpenAI.Codex) |
| Codex CLI | 0.149.0-alpha.4.3 (originator: Codex Desktop) |
| documents skill | 26.819.11345 |
| Bundled Python | 3.12.13 AMD64 |
| Bundled poppler | 26.05.0 |

What steps can reproduce the bug?

Precondition: the process PATH must not contain <RUNTIME>\dependencies\native\poppler\Library\bin. An environment where that directory has been added manually will succeed — see the workaround in Additional information.

<RUNTIME>\dependencies\python\python.exe ^
  <PLUGINS>\documents\26.819.11345\skills\documents\render_docx.py ^
  test.docx --output_dir out --emit_pdf

Actual result: PDFInfoNotInstalledError. out\test.pdf is created; no PNG is produced.

A/B check on the same machine, same minute, same .docx, same skill version — the only variable is whether the bundled native bin directory is present in the process PATH:

without  ->  PDFInfoNotInstalledError      0 PNG
with     ->  Pages rendered to ...         1 PNG

What is the expected behavior?

Pages rendered to out, producing the PDF and one PNG per page — without the user having to modify PATH manually.

Additional information

Analysis

Two conditions together form the failure chain, and a third explains why an obvious-looking remedy does not work either.

1. _prepend_bundled_runtime_bin() adds nothing on this layout.

In skills/documents/render_docx.py, neither discovery branch fires:

  • The first branch tests basename(dirname(dirname(sys.executable))) == "python". For the bundled interpreter at <RUNTIME>\dependencies\python\python.exe, that expression evaluates to "dependencies", so the test fails. The expression appears incompatible with the current bundled interpreter layout — it would match something like python\bin\python.exe. I cannot tell from the installed artifacts alone which layout it was written against.
  • The second branch walks up from __file__ looking for an ancestor directory named codex-primary-runtime. The skill is installed in the plugins tree, which does not contain that path segment, so this branch cannot fire either.

Net effect: the function is a no-op here, and dependencies\bin\override is never added to PATH.

2. bin\override contains only a manifest, no executables.

dependencies\bin\override\
  native-executables.json      <- manifest only

The manifest declares four native tools by relative path:

{
  "heif-convert": "../../native/libheif/libheif/bin/heif-convert.exe",
  "JxrDecApp":    "../../native/jxrlib/jxrlib/bin/JxrDecApp.exe",
  "pdfinfo":      "../../native/poppler/Library/bin/pdfinfo.exe",
  "pdftoppm":     "../../native/poppler/Library/bin/pdftoppm.exe"
}

No corresponding executables or wrappers exist in that directory.

These two conditions are chained, not independent. Fixing (1) alone would prepend an override directory that still contains no executables; fixing (2) alone would populate a directory that is still never added to PATH.

3. Why adding native\poppler\bin to PATH is not a remedy either.

dependencies\native\poppler\bin\ does contain pdfinfo.cmd / pdftoppm.cmd, but these cannot satisfy the lookup. pdf2image calls subprocess.Popen(["pdfinfo", ...]) with shell=False. On Windows this reaches CreateProcess, which appends only .exe when searching PATH. PATHEXT — what makes .cmd resolvable — is a cmd.exe / PowerShell behaviour, not a CreateProcess one.

Verified directly: with native\poppler\bin on PATH, the bundled Python still raises FileNotFoundError: [WinError 2].

This also explains why the bug is easy to misdiagnose. Invoking pdfinfo by hand in PowerShell succeeds, because PowerShell resolves .cmd. Manual verification therefore looks healthy while every skill invocation fails.

Additional observation

Two files in the installed runtime have modification times 14 seconds apart:

dependencies\bin\override\native-executables.json   2026-08-20 06:04:58
dependencies\bin\fallback\pnpm.cmd                  2026-08-20 06:05:12

This suggests the output-generation / write path for override did not behave as intended during runtime setup.

Evidence boundary: these timestamps do not establish that fallback and override are produced by the same materializer, nor do they pin the exact failing branch. Noted only as a pointer for investigation.

Suggested fixes

  1. Script-scoped (smallest, bypasses the whole chain). Derive the dependencies root from sys.executable, locate native\poppler\Library\bin, and pass it explicitly as poppler_path= to pdfinfo_from_path() and convert_from_path(). This depends on neither wrapper materialization, nor PATH, nor .cmd resolution, and it does not expose a directory of general-purpose DLLs to unrelated processes.
  2. Runtime-level. Materialize the manifest entries as artifacts CreateProcess can resolve (real .exe — copies or launchers), or inject the exact native bin directories into the child process PATH. .cmd wrappers will never satisfy a shell=False lookup.
  3. Path discovery. Make both branches of _prepend_bundled_runtime_bin() work when the plugin tree and the runtime tree are separate.

Workaround currently in use

Appending the real binary directory to the end of the user PATH:

<RUNTIME>\dependencies\native\poppler\Library\bin

It must be Library\bin (real .exe), not bin (.cmd only). I additionally appended the libheif and jxrlib bin directories pre-emptively, since the same manifest declares them; I did not observe or attempt to reproduce a failure involving those two tools.

Appending rather than prepending is deliberate: these directories also contain general-purpose runtime DLLs (ucrtbase.dll, vcruntime140.dll, libcrypto-3-x64.dll, icu*.dll, zlib.dll, …). Placing them earlier in PATH could shadow those libraries for unrelated processes. Appended at the very end, the change cannot alter any DLL resolution that already succeeds.

This is a workaround, not a fix: the paths live under .cache, so it breaks again whenever the runtime directory moves.

View original on GitHub ↗