Documents render_docx.py fails on Windows with LibreOffice temporary UserInstallation profile

Open 💬 5 comments Opened May 23, 2026 by Ryan0952

Summary

The Documents plugin renderer render_docx.py fails on Windows when it launches LibreOffice with a temporary UserInstallation profile. Direct LibreOffice conversion of the same DOCX succeeds, and Poppler PDF-to-PNG conversion succeeds, so the failure appears tied to the temporary LibreOffice profile invocation used by the renderer.

Environment

  • OS: Windows 10 Home
  • Codex CLI: codex-cli 0.130.0-alpha.5
  • Codex Desktop App on Windows
  • Documents plugin bundle/version: openai-primary-runtime/documents/26.521.10419
  • LibreOffice: 26.2.3.2
  • Poppler / pdftoppm: installed and usable

Observed behavior

Running the Documents skill renderer against a simple generated DOCX fails before producing the intermediate PDF/PNGs:

RuntimeError: Failed to produce PDF for rasterization (direct and ODT fallback).
--- DOCX->PDF ---
CMD: soffice -env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx --invisible --headless --norestore --convert-to pdf --outdir C:\Users\<USER>\AppData\Local\Temp\soffice_convert_xxx C:\path\to\render-test.docx
EXIT: 1
STDERR:
libpng error: Write Error
--- DOCX->ODT ---
CMD: soffice -env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx --invisible --headless --norestore --convert-to odt --outdir C:\Users\<USER>\AppData\Local\Temp\soffice_convert_xxx C:\path\to\render-test.docx
EXIT: 1
STDERR:
libpng error: Write Error

The same error persisted after:

  • MSI repair via msiexec.exe /fa {product-code}
  • winget install --id TheDocumentFoundation.LibreOffice -e --silent --force

Expected behavior

render_docx.py should successfully render a DOCX to page PNGs on Windows when LibreOffice and Poppler are installed, or should generate a Windows-compatible LibreOffice UserInstallation URI.

Reproduction outline

  1. On Windows, create a simple DOCX, e.g. a heading and one paragraph.
  2. Run the Documents plugin renderer:
python render_docx.py C:\path\to\render-test.docx --output_dir C:\path\to\rendered --emit_pdf
  1. Observe the failure above.

Additional evidence

Direct LibreOffice conversion works for the same file:

& 'C:\Program Files\LibreOffice\program\soffice.exe' --headless --convert-to pdf --outdir C:\path\to\out C:\path\to\render-test.docx

The PDF is produced successfully.

Poppler PDF-to-PNG conversion also works:

pdftoppm -png C:\path\to\render-test.pdf C:\path\to\page

The PNG is produced successfully.

This provides a viable workaround:

DOCX -> LibreOffice direct PDF export -> pdftoppm PNG rasterization

Suspected cause

render_docx.py appears to build the LibreOffice profile argument by concatenating:

-env:UserInstallation=file:// + user_profile

On Windows this produces a value like:

file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx

That may not be a valid/stable file URI for LibreOffice on Windows. A more Windows-compatible URI would likely be closer to:

file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx

Using pathlib.Path(user_profile).as_uri() or equivalent URI normalization may be safer.

Notes

No customer documents were used for this report. The failure was reproduced with a trivial test DOCX.

View original on GitHub ↗

5 Comments

jshaofa-ui · 3 months ago

Proposed Solution: LibreOffice Windows Temp Profile Cleanup Race Condition

Root Cause

On Windows, tempfile.TemporaryDirectory() cleanup fires before LibreOffice releases file handles to the UserInstallation profile directory. Windows file locking semantics keep handles open until the process fully terminates, causing PermissionError: [WinError 32].

Fix

Replace TemporaryDirectory context manager with mkdtemp() + Windows-aware cleanup with retry:

temp_dir = tempfile.mkdtemp()
try:
    # LibreOffice conversion...
finally:
    if os.name == 'nt':
        for attempt in range(5):
            try:
                shutil.rmtree(temp_dir, onerror=_on_rm_error)
                break
            except PermissionError:
                time.sleep(0.5 * (attempt + 1))

Impact

  • Severity: High - DOCX rendering completely broken on Windows
  • Scope: All Windows users of Codex Documents plugin

Testing

  1. Unit test: Verify temp dir cleanup succeeds on Windows after LibreOffice conversion
  2. Integration test: Full DOCX to PDF conversion pipeline on Windows

Full solution: solutions/codex-24210-libreoffice-windows-temp-profile-fix.md

Ryan0952 · 3 months ago

Thanks for taking a look and proposing this. I agree that replacing TemporaryDirectory() with mkdtemp() plus Windows-aware retry cleanup is a good defensive fix for LibreOffice on Windows, especially for PermissionError: [WinError 32] cases where soffice.bin still holds profile files open.

One additional data point from my reproduction: the failure I am seeing appears to happen before temp directory cleanup. The renderer fails during the DOCX -> PDF / DOCX -> ODT conversion step itself:

EXIT: 1
STDERR:
libpng error: Write Error

At that point no intermediate PDF is produced, so this does not look like only a post-conversion cleanup race in my environment.

I think the cleanup retry would still be useful, but the Windows UserInstallation argument may also need attention. The current command shown by the renderer looks like:

-env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx

I suspect LibreOffice on Windows may be more reliable with a normalized file URI such as:

file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx

or whatever pathlib.Path(profile).as_uri() produces.

For comparison, direct conversion without the temporary profile works on the same machine/file:

soffice --headless --convert-to pdf --outdir <outdir> render-test.docx

and then pdftoppm -png works on the resulting PDF.

So my current guess is that a robust Windows fix may need both:

  1. normalized Windows file URI handling for UserInstallation, and
  2. retry cleanup / process-lifecycle handling after LibreOffice exits.

Happy to test again if a patch or updated plugin build becomes available.

dp1795 · 2 months ago

Confirmed on a newer Documents bundle as well (openai-primary-runtime/documents/26.601.10930), and I was able to turn this into a working local fix.

There appear to be two Windows-specific bugs in render_docx.py, not just one:

  1. UserInstallation URI is malformed on Windows

The current renderer builds:

-env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx

That is not a valid Windows file URI for LibreOffice. Changing it to a real file URI fixes the profile argument:

from pathlib import Path

user_installation_arg = "-env:UserInstallation=" + Path(user_profile).resolve().as_uri()

which produces:

-env:UserInstallation=file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx
  1. On Windows, bare soffice should not be used for headless scripted runs

In this environment, subprocess.run(["soffice", ...]) ended up launching soffice.exe (GUI launcher), not the console-safe soffice.com, which is consistent with the misleading popup/hang behavior people are seeing.

Process inspection showed commands like:

soffice -env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx --invisible --headless --norestore --convert-to pdf ...

and the actual Windows process tree contained soffice.exe / soffice.bin rather than an explicit soffice.com invocation.

For the renderer, explicitly resolving soffice.com on Windows avoids the GUI path:

def _resolve_soffice_executable() -> str:
    if os.name != "nt":
        return shutil.which("soffice") or "soffice"

    candidates = [shutil.which("soffice.com")]
    for env_var in ("ProgramFiles", "ProgramFiles(x86)"):
        root = os.environ.get(env_var)
        if root:
            candidates.append(os.path.join(root, "LibreOffice", "program", "soffice.com"))
    candidates.append(shutil.which("soffice"))

    for candidate in candidates:
        if candidate and os.path.exists(candidate):
            return candidate
    return "soffice.com"

and then use that resolved executable in the conversion commands instead of bare "soffice".

Result after patch

With both changes applied locally:

  • the renderer invoked
C:\Program Files\LibreOffice\program\soffice.com -env:UserInstallation=file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx --invisible --headless --norestore --convert-to pdf ...
  • LibreOffice successfully produced the intermediate PDF
  • render_docx.py completed with exit code 0
  • the final page-1.png was written successfully from a trivial test DOCX using the bundled Codex Python/runtime

So from this reproduction, the robust Windows fix looks like:

  1. normalize UserInstallation with Path(...).as_uri()
  2. use explicit soffice.com for Windows headless/scripted execution
  3. keep the previously suggested retry/cleanup hardening as a separate defensive improvement for post-conversion file-handle races

I think (1) and (2) are the changes that actually unblock rendering in the failing path.

KIDA-MNESIA · 2 months ago

Additional confirmation from another Windows/Codex Desktop reproduction.

The user-visible symptom can be a LibreOffice GUI dialog rather than only a renderer traceback:

Cannot start application.
Configuration file "C:\Program Files\LibreOffice\program\bootstrap.ini" is corrupted.

In this reproduction, bootstrap.ini was not actually corrupted. LibreOffice itself was usable (soffice --version worked and direct DOCX -> PDF conversion worked). The failing renderer command was the same malformed Windows UserInstallation URI shape already discussed here:

-env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx

After changing the renderer to build the argument with:

from pathlib import Path

"-env:UserInstallation=" + Path(user_profile).resolve().as_uri()

LibreOffice received:

-env:UserInstallation=file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx

and the bootstrap.ini dialog no longer appeared. The Documents renderer was then able to produce the intermediate PDF from a trivial DOCX using bundle openai-primary-runtime/documents/26.601.10930 and LibreOffice 26.2.3.2.

One related follow-up: on this Windows environment, once the LibreOffice URI issue was fixed, the next failure was PDF rasterization because Poppler was not available in PATH:

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

Installing pypdfium2 into the bundled Codex Python runtime and using it as a fallback allowed the renderer to complete with exit code 0 and write page-1.png. That is separate from the LibreOffice URI bug, but it means a robust Windows Documents fix may also need either bundled Poppler or a non-Poppler PDF rasterization fallback.

huangxuanminggithub · 1 month ago

Additional confirmation from openai-primary-runtime/documents/26.630.12135 on Windows.

I hit the same renderer failure path and was able to get a local hotfix working. There were two separate Windows issues in this bundle:

  1. The LibreOffice UserInstallation argument is still built as a malformed Windows file URL:
-env:UserInstallation=file://C:\Users\<USER>\AppData\Local\Temp\soffice_profile_xxx

In my repro this caused soffice.bin to hang and produce no intermediate PDF. Replacing that with a real file URI fixed the DOCX -> PDF step:

from pathlib import Path

user_installation_uri = Path(user_profile).resolve().as_uri()
# file:///C:/Users/<USER>/AppData/Local/Temp/soffice_profile_xxx
  1. After the LibreOffice URI fix, rasterization failed with:
pdf2image.exceptions.PDFInfoNotInstalledError: Unable to get page count. Is poppler installed and in PATH?

In this environment Poppler was actually bundled, but the renderer did not locate the real binaries. The available command wrappers were under:

<codex-runtime>/dependencies/bin/pdfinfo.cmd
<codex-runtime>/dependencies/bin/pdftoppm.cmd

while the real executables were under:

<codex-runtime>/dependencies/native/poppler/Library/bin/pdfinfo.exe

Passing that real Poppler directory to pdf2image fixed the PDF -> PNG step:

poppler_path = os.path.join(
    dependencies_root, "native", "poppler", "Library", "bin"
)

pdfinfo_from_path(pdf_path, poppler_path=poppler_path)
convert_from_path(pdf_path, ..., poppler_path=poppler_path)

After applying both local changes, I verified:

  • a trivial DOCX rendered successfully to page-1.png
  • a multi-page DOCX rendered successfully to PNGs
  • a complex DOCX with a non-ASCII filename also rendered successfully

Environment:

Windows
LibreOffice 26.2.3.2
openai-primary-runtime/documents/26.630.12135
bundled Codex Python runtime

So for the current bundle, the robust Windows fix likely needs both:

  1. normalize UserInstallation with Path(...).resolve().as_uri()
  2. explicitly pass the bundled real Poppler binary directory (dependencies/native/poppler/Library/bin) to pdf2image, rather than relying on PATH / .cmd wrappers