soffice-runtime-fix
Recover the render path the active consumer actually uses. Diagnose before
mutating the runtime, and verify document content rather than treating
soffice --version or the existence of a PNG as proof of success.
Trigger and boundary
Use this skill when bundled LibreOffice fails with a dylib, profile, cache, or Fontconfig error; when DOCX-to-PDF fails after a runtime update; or when a PDF is produced but Thai text disappears.
First locate the failing stage:
DOCX -> PDF: launcher/topology, dylib, profile/cache, or font integrity.PDF -> PNG: PyMuPDF, Poppler, output-path, or image-generation failure.- DOCX package/content: invalid input rather than a runtime fault.
Missing PyMuPDF alone is a rasterizer failure. Prefer bundled pdftoppm when
available and do not patch LibreOffice unless DOCX-to-PDF or font integrity is
also broken.
Workflow
Call
load_workspace_dependenciesand record the dependency root and bundle version. Identify the renderer used by the current task.Diagnose without mutation:
python scripts/repair_soffice_runtime.py \ --diagnose \ --dependency-root <dependencies> \ --bundle-version <version> \ --renderer <render_docx.py> \ --jsonCandidate order is the renderer-resolved launcher, then
bin/override/soffice, then legacybin/soffice. Do not create the legacy path merely to make the repair script run.Repair only failure classes
AthroughD:A: launcher missing or consumer topology mismatchB: dyld or hard-coded Homebrew referenceC: LibreOffice profile or writable-cache failureD: PDF created but Thai text/font is missingE: PDF created but no rasterizer; use fallback, no LibreOffice patchF: output-path or unrelated renderer failure; investigate that layerG: invalid DOCX package/content; repair the document or generator
python scripts/repair_soffice_runtime.py \ --repair \ --dependency-root <dependencies> \ --bundle-version <version> \ --renderer <render_docx.py> \ --verify-docx <thai-and-omml-smoke.docx> \ --knowledge-doc <project>/outputs/soffice-docx-render-fix-knowledge.md \ --jsonRuntime-cache mutation may require escalation.
--dry-runreports the intended launcher, patches, and backup without changing files.If the project has no suitable fixture, create the bundled one:
python scripts/create_smoke_fixture.py <temporary-output>/thai-omml-smoke.docxRun the same renderer command the document task will use. The smoke DOCX must contain Thai text, Latin text/numbers, and editable OMML. Require a non-empty PDF, at least one PNG, and evidence of an embedded Thai font when the DOCX contains Thai.
Open at least one rendered smoke page during a release/regression check. Then rerun diagnosis: it must be healthy or a no-op and must not create a second backup.
Repair invariants
- Back up the active launcher before changing it; include its original checksum in the backup name so reruns remain idempotent and reversible.
- Generate wrapper paths relative to the launcher's real directory. A wrapper
in
bin/overrideneeds different relative paths from one inbin. - Use a temporary LibreOffice profile, writable cache, and a minimal generated Fontconfig file that names macOS font directories. Do not reuse the full Poppler Fontconfig configuration merely because its libraries are bundled.
- Patch only known Homebrew references whose bundled replacement exists. Leave an unmapped reference visible and fail safely.
soffice --versionproves launchability only. It never proves Thai or OMML render integrity.
Status and evidence
The script emits HEALTHY, LAUNCH_HEALTHY_UNVERIFIED, REPAIRED_DYLIB,
REPAIRED_WRAPPER, THAI_RENDER_FAILURE, RASTERIZER_MISSING, or
UNSUPPORTED_TOPOLOGY in JSON or human-readable form. The unverified launch
status means --version passed but no content-bearing DOCX was checked. Preserve
exact failure signatures and retained warnings.
Knowledge entries are keyed by date, bundle version, active launcher, skill version, and failure stage so multiple repairs on one day are not discarded.
Treat LibreOffice output as a layout sanity check, not final Microsoft Word truth. Resume the original document task only after its real renderer passes.