cloudflare / cloudflare/computer
pandoc --pdf-engine fails with "openFile: resource busy (file is locked)" on deployed Containers — the tutorial's PDF flow cannot work in production
- Dominant language
- TypeScript
- Stars
- 9.2k
- Forks
- 513
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 24
Description
## Summary
The PDF-conversion flow the tutorial documents (`pandoc card.md -o card.pdf --pdf-engine=typst`) fails on **real, deployed Cloudflare Containers** with:
```
pandoc: openFile: resource busy (file is locked)
HasCallStack backtrace:
bracket, called at ./System/IO/Temp.hs:114:3 in temporary-1.3-…:System.IO.Temp
```
The same command works under `wrangler dev` (local Docker), so the failure is invisible until first deploy. The trigger is narrow: it is specifically the `--pdf-engine` path's temp-file staging (`System.IO.Temp` / the `temporary` package, i.e. GHC's file locking on the temp file). Plain pandoc output files and ordinary shell writes to the same filesystem are fine.
## Reproduction
`examples/container` from this repo at `76d9e75`, deployed unmodified except for two Dockerfile changes: computerd tag bumped to `0.1.1` (to match the published `@cloudflare/computer@0.1.1` the example was installed with), and `pandoc 3.10.1` + `typst 0.15.1` added to the image (same tools the tutorial uses). `wrangler 4.119`, instance type `standard-2`.
All probes are plain `curl` against the deployed example's own HTTP surface:
**Control — the filesystem accepts writes (shell):**
```json
POST /c/repro1/exec {"command":"sh -c 'echo ctrl > /tmp/ctrl.txt && cat /tmp/ctrl.txt'"}
→ {"status":"completed","exitCode":0,"stdout":"ctrl\n"}
```
**Control — plain pandoc output file works (no engine involved):**
```json
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -t docx -o /tmp/out.docx -"}
→ {"status":"completed","exitCode":0}
```
**Failure — any `--pdf-engine` invocation, container-local /tmp:**
```json
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -o /tmp/out.pdf --pdf-engine=typst -"}
→ {"status":"failed","exitCode":1,"stderr":"pandoc: openFile: resource busy (file is locked)\nHasCallStack backtrace:\n bracket, called at ./System/IO/Temp.hs:114:3 …"}
```
**Failure — the tutorial's exact flow (input written host-side, synced to the mount — note `pushed: 2`, sync itself works fine):**
```json
PUT /c/repro1/file/workspace/card.md ("# Karta…")
POST /c/repro1/exec {"command":"pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst"}
→ {"status":"failed","exitCode":1,"stderr":"pandoc: openFile: resource busy (file is locked)…","pushed":2,"sync":{"status":"complete","applied":3}}
```
## Analysis
GHC takes an fcntl lock when `System.IO.Temp` opens the temp file that the `--pdf-engine` path stages the intermediate document into, and the deployed Container filesystem refuses that lock (`EBUSY`-shaped failure surfaced as "resource busy (file is locked)"). Local Docker's filesystem grants the lock, hence the dev/prod split. Ordinary `openFile` for pandoc's `--output` does not trip it (the docx control above), so the blast radius is exactly "anything that goes through pandoc's PDF-engine temp staging".
## Working workaround (verified on the same deployment)
Skip `--pdf-engine` entirely: have pandoc emit standalone typst markup to **stdout** (shell owns the file), then compile with typst (Rust — no GHC locking):
```json
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -t typst -s - > /tmp/x.typ && typst compile /tmp/x.typ /tmp/x.pdf && ls -la /tmp/x.pdf"}
→ {"status":"completed","exitCode":0,"stdout":"-rw-r--r-- 1 root root 7004 … /tmp/x.pdf\n"}
```
Possibly worth a note in the tutorial until the underlying lock behavior is addressed on the Containers filesystem — as-is, the tutorial's flagship command works in every local run and fails on the first production deploy, which is an expensive way to find out.
Happy to provide the deployed-example wrangler config or run further probes if useful.
Contributor guide
Assessment
This issue has not been assessed yet.