Repository navigation
Fix renders failing with RangeError when a Quarto cache is damaged - #15185
Merged
Merged
Conversation
Deno KV reports a damaged cache through plain RangeError/TypeError/Error messages with no code or distinct class, so src/deno_ral/kv.ts classifies them by message text: a damaged value read through getKv is a miss with one warning per file, a damaged file raises KvDamagedError naming the folder to delete, and anything else is rethrown with the cache path. The tests damage real stores (node:sqlite edits, truncation, a text file, a zeroed b-tree page header) instead of mocking errors, so a Deno bump that rewords one of these messages fails a test rather than silently disabling the handling. printsMessage gains a count option so the once-per-file warning can be asserted.
A damaged ["version"] row in the user-global Sass cache made every render fail with a bare "RangeError: could not deserialize value", the error in the report. Reading it through getKv turns the damaged value into null, so the existing null branch of checkVersion rewrites the row: the store repairs itself on the next render. A damaged entry row is a miss that the following setFromHash overwrites. A damaged file (truncated, or not SQLite) still fails the render, but the message now names the file and the v8-<branch> folder to delete. Quarto never deletes it: another process may hold the store, and on POSIX SQLite later removes the -wal/-shm files by path. If the version check fails after the store opened, the handle is closed so the folder stays deletable on Windows.
The project cache index (.quarto/project-cache/v8-<branch>/deno-kv-file) had the same failure shape as the Sass cache: a damaged version row or entry aborted the render with "could not deserialize value". Routing its KV calls through the runtime layer makes a damaged value a miss that the next write repairs, and a damaged file an error naming the folder to delete. createOnDisk() now awaits its set. Without the await, the repair write for a damaged version row ran detached, init() re-read the version before it landed, and a malformed-page failure rejected outside createProjectCache(). A failed init() closes the handle it opened, so a store that opens but fails its version read stays deletable on Windows. A locked index is not damage: it surfaces as Deno's "database is locked" with the cache path appended.
src drops by two: both caches now open their store through src/deno_ral/kv.ts instead of calling Deno.openKv directly. tests rise because the damage fixtures build, damage and inspect real Deno KV stores (Deno.openKv, truncateSync, a lock-holding child process), which a mocked error could not replace. tests.stdImportFiles drops by one because sass-cache.test.ts now imports its assertions from tests/asserts.ts.
Adds the kv.ts row to the deno_ral module table and the #15119 changelog entry.
quarto preview re-resolves the cache on every re-render, so a second resolve on the same damaged store must give the user the same actionable message, not a different error from leftover state.
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a Quarto cache file is damaged,
quarto checkand every render fail withERROR: RangeError: could not deserialize value, with no hint at which file is at fault. In #15119 the user Sass cache (~/.cache/quarto/sass) got damaged after restoring a home folder from an external drive, and deleting that folder by hand was the only way out.Root cause
Both caches keep their index in Deno KV: the Sass cache in
src/core/sass/cache.tsand the project cache insrc/core/cache/cache.ts. Akv.get()on a row whose value can't be deserialized throws, and nothing caught it, so the error ended the render. Reproduced with 1.10.19 by zeroing the value of the Sass cacheversionrow withsqlite3.Fix
A new
src/deno_ral/kv.tswraps every KV call of both caches and sorts errors by message, as Deno gives them no code:could not deserialize value, an unknown value encoding, an invalid column type) is read as a cache miss. Quarto warns once per cache file, and the write that follows the miss repairs the row. This is the ERROR: RangeError: could not deserialize value #15119 case.database disk image is malformed,file is not a database) fails with a single error line naming the file and the folder to delete.Quarto never deletes a cache file itself. Deleting a store that another Quarto process has open is unsafe with SQLite, and the forms of damage found while testing are open-ended, so deleting the folder is left to the user with the error saying which one.
The new tests build really damaged stores (values zeroed with
node:sqlite, truncated files, a text file in place of the database) instead of mocking errors.Checklist
I have (if applicable):
AI-assisted PR
Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.
Fixes #15119