🐛 Fixed post card assets going missing until restart (minimal disk recovery)#29129
🐛 Fixed post card assets going missing until restart (minimal disk recovery)#29129sagzy wants to merge 1 commit into
Conversation
ref https://linear.app/ghost/issue/ONC-1865 - cards.min.css/js are generated into content/public at runtime; when the files went missing or the folder was unwritable, every request 404d until reboot because nothing ever retried the build - a missing generated file now triggers one shared rebuild and a single retried read; concurrent requests join the in-flight build instead of 404ing - build policy (single-flight, 10s retry backoff, rebuild-on-theme-change) lives on the assets service so every entry point shares one throttle - the minifier now writes atomically (temp file + rename) so a request can never read a half-written asset, and built files are no longer pinned in the middleware memory cache where a bad read would stick until restart Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run ghost:test:ci:integration |
✅ Succeeded | 2m 45s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 2m 47s | View ↗ |
nx run ghost:test:legacy |
✅ Succeeded | 2m 56s | View ↗ |
nx run ghost:test:e2e |
✅ Succeeded | 2m 22s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | <1s | View ↗ |
nx run-many -t lint -p ghost |
✅ Succeeded | 35s | View ↗ |
nx run-many -t test:unit -p ghost |
✅ Succeeded | 29s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 5s | View ↗ |
Additional runs (2) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-06 09:49:10 UTC
| // whose card config legitimately yields no output must 404 fast instead | ||
| // of triggering a rebuild per request. | ||
| const rebuildCardAssets = type => () => (cardAssets.hasFile(type) ? cardAssets.ensureLoaded() : Promise.resolve()); | ||
| siteApp.get('/public/cards.min.css', cardAssets.serveMiddleware(), createPublicFileMiddleware('built', 'public/cards.min.css', 'text/css', config.get('caching:publicAssets:maxAge'), {rebuild: rebuildCardAssets('css')})); |
| // of triggering a rebuild per request. | ||
| const rebuildCardAssets = type => () => (cardAssets.hasFile(type) ? cardAssets.ensureLoaded() : Promise.resolve()); | ||
| siteApp.get('/public/cards.min.css', cardAssets.serveMiddleware(), createPublicFileMiddleware('built', 'public/cards.min.css', 'text/css', config.get('caching:publicAssets:maxAge'), {rebuild: rebuildCardAssets('css')})); | ||
| siteApp.get('/public/cards.min.js', cardAssets.serveMiddleware(), createPublicFileMiddleware('built', 'public/cards.min.js', 'application/javascript', config.get('caching:publicAssets:maxAge'), {rebuild: rebuildCardAssets('js')})); |

ref https://linear.app/ghost/issue/ONC-1865
Problem
/public/cards.min.cssand/public/cards.min.jsare minified at runtime intocontent/public/and served by reading them back from disk. If the built files go missing at runtime, or the content folder is unwritable (EACCES), every request 404s until the process restarts — ~486 production sites accumulated in this state during Jun 25–29.Solution: keep disk serving, fix the root causes
This keeps the existing serve-from-disk architecture and makes the smallest set of changes that fix the failure modes at the layer that owns them:
AssetsMinificationBase.ensureLoaded(): concurrent callers join a single in-flight build; a 10s retry backoff (also covering builds that resolve without producing assets, e.g. swallowedEACCES) stops a persistently failing build from minifying per request;invalidate()no longer forgets an in-flight build — a generation counter re-runsload()with the new config instead of allowing two concurrent minifier runs.ENOENTfor a built file it calls the service's rebuild hook once and retries the read once. No throttle at this layer: a request arriving during an in-flight rebuild joins it instead of 404ing.hasFile()gate — files the current build legitimately never produces (theme card config yielding no output) 404 fast instead of triggering a rebuild per request.serveMiddleware()no longer lets a rejectedload()escape as an unhandled rejection (which left requests hanging under Express 4) — this also fixes the hang forAdminAuthAssets.Testing
ensureLoaded()(join-in-flight, backoff after failure and after resolved-but-not-ready builds, invalidate resets backoff, mid-build invalidate re-runs load with no concurrency), and the recovery path (rebuild→200, still-missing→404 with exactly one rebuild, rebuild rejection logged→404, static files never rebuild, built files not memory-cached while static still are).ghost/coreunit suite: 7185 passed; the 3 failures are pre-existing onmain(verified by baseline run).🤖 Generated with Claude Code