Demo budget errors propagated through document preview and download routes

Document previews were returning 404s when the real failure was a `DemoBudgetExceededError` from Case Vault. kveton traced the masking to the storage helpers swallowing the error into null, then rewired the display, DOCX, URL, and download-token routes to surface a 402 instead.

infrastructurechat-ui

storage.ts was catching all errors from downloadFile() and getSignedUrl() and returning null on failure. That meant a demo budget error looked like a missing document to the routes above, which then responded with 404. The fix adds if (isDemoBudgetError(err)) throw err in both catch blocks, letting the budget error escape and be handled explicitly by the route.

Each affected route -- display, DOCX, URL, and the download-token endpoint -- now wraps the storage call in a try/catch that calls sendDemoBudgetError() first. That helper returns 402 with code: "demo_budget_exceeded" and a fixed user-facing message pointing the user to Account > Models. A follow-up commit hardened the message text across documents.ts and downloads.ts to use a shared constant rather than inline strings.

The DOCX route was also changed from a redirect to a byte proxy here, matching the earlier display-route change: downloadFile() bytes are buffered and sent with Content-Disposition: inline rather than redirecting to a presigned URL.

On the frontend, PDF and DOCX viewers now render a specific "demo credit limit reached" message instead of the generic failed-to-load UI. Demo usage metering was tuned in the same PR: non-LLM calls (Vault, Legal, Skills, Matters) default to $0.00, and the LLM reserve dropped from $0.75 to $0.10, with actual cost charged from usage.cost or live catalog token pricing where available.

So what Skip unless you're running the hosted demo mode or have a similar pattern where a budget-enforcement error can be mistaken for a missing resource. The `isDemoBudgetError(err) throw err` pattern in the storage helpers is the key fix; the rest is propagation plumbing.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

3 commits from CaseMark/mikeoss-casedotdev, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
5e899345 Surface demo budget errors for document loads kveton 2026-05-04 ↗ GitHub
c4a955e2 Tune demo usage metering kveton 2026-05-04 ↗ GitHub
57ba208c Harden document load error responses kveton 2026-05-04 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-282.md from inside the repo you want the changes in.

⬇ Download capture-thread-282.md