fix(state): drop a stale stat:file from the lockfile once content upgrades it - #148
Merged
Conversation
…rades it
A path only stat'd under --fs records a payload-free `stat:file` (a formats tag,
no hash/bytes). When a SEPARATE --lock=add run absorbs that lockfile and then
fs-reads the path, addFile deletes the tag from the live `this.formats` and
records real content -- but for a loader-authoritative .js/.ts (module vs
commonjs is the loader's call) no replacement format is produced. #mergedFormats
then re-seeded the `stat:file` from the absorbed #lockFormats with nothing to
override it, so the lockfile attested BOTH a content hash and a payload-free
stat:file for the same path: it broke the "stat records are payload-free"
invariant and disagreed with the bundle (whose formats come from `this.formats`,
which had the tag deleted). A later frozen run that imported the file tripped
#assertAttestedFormat's strict equality ('module' !== 'stat:file').
Enforce the invariant at the merge boundary: a stat:* never survives on a file
that carries a content hash. Extension-determinable formats (.mjs/.json/
resource/...) already win the upsert, so only the genuinely-ambiguous .js/.ts
falls through to no format -- matching the bundle, and served at load by content
presence (getFsStat), not a format tag.
Tests: a cross-run stat->fs-read upgrade of a .js leaves no stale stat:file
beside its hash (and .mjs/.json keep their real format); getFsStat answers
isFile on content with no format; lockData keeps a pure stat-only record but
drops one superseded by content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHHftnppTgJEkEsUP7hqTg
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.
Summary
A path only stat'd under
--fsrecords a payload-freestat:file(aformatstag, no hash/bytes). When a separate--lock=addrun absorbs that lockfile and then fs-reads the path, the lockfile ended up attesting both a content hash and a payload-freestat:filefor the same path — breaking the "stat records are payload-free" invariant and disagreeing with the bundle.Root cause
addFiledeletes thestat:*tag from the livethis.formatswhen content arrives, but only writes a replacement format when one is known. A loader-authoritative.js/.ts(module vs commonjs is the loader's call) produces no format.#mergedFormatsrebuilds the lockfile's formats by seeding from the absorbed#lockFormats(which still holdsstat:file) and overlayingthis.formats. With no entry inthis.formatsto override it, the stalestat:filesurvived next to the new hash.this.formatsdirectly, so it correctly had no format — hence the lockfile/bundle disagreement.#assertAttestedFormat's strict equality ('module' !== 'stat:file').Only genuinely-ambiguous
.js/.tswere affected; extension-determinable formats (.mjs/.json/resource/…) already win the merge and never desynced.directory/stat:directoryare also unaffected — a realdirectoryoverridesstat:directoryviareconcileFormat.Fix
Enforce the invariant at the merge boundary in
#mergedFormats: astat:*never survives on a file that carries a content hash. The genuinely-ambiguous.js/.tsfalls through to no format — matching the bundle, and served at load by content presence (getFsStat), not a format tag.Tests
tests/cli-fs.test.js— a cross-runstat→ fs-read upgrade of a.jsleaves no stalestat:filebeside its hash;.mjs/.jsonkeep their real format;lstatSync().isFile()still works at load with the file gone from disk.tests/fs.test.js—getFsStatanswersfileon content with no format;lockDatakeeps a pure stat-only record but drops one superseded by content.Verification
oxlintclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01JHHftnppTgJEkEsUP7hqTg
Generated by Claude Code