Skip to content

Let a real format always supersede a payload-free stat record#68

Merged
ChALkeR merged 1 commit into
mainfrom
claude/lstat-sync-recording-chst5u
Jul 17, 2026
Merged

Let a real format always supersede a payload-free stat record#68
ChALkeR merged 1 commit into
mainfrom
claude/lstat-sync-recording-chst5u

Conversation

@exo-nikita

Copy link
Copy Markdown
Collaborator

Fixes a capture-aborting regression from #66, hit in real --fs runs:

[stasis] --fs: capture aborted, no bundle/lockfile will be written -- Conflict for "node_modules/@exodus/stasis-core/src/util.js"
+'module'
-'stat:file'

Root cause

addFsStat's "already recorded" skip keyed off byte records (hashes/sources/resources) only — but addImport attests a resolved target's format at resolve time, with no byte record when the load hook never fires for it. Stasis-core's own preload-cached modules are exactly that shape: the resolve hook records module for node_modules/@exodus/stasis-core/src/util.js, the load hook is skipped (the module was cached before hooks registered), and when a bundler's enhanced-resolve under --fs then stats that path, the stat record hit noupsert against the attested 'module' and aborted the whole capture. The reverse ordering (path stat'd first, then an import attesting its real format through addImport) conflicted symmetrically.

Fix

One shared rule, upsertFormat(map, file, format): a real format always takes priority over a stat:* record — it replaces a stat record already present, and an arriving stat record yields to (never displaces, never conflicts with) an existing real format. Used by addFsStat, addImport, and #mergedFormats (replacing its duplicate local upgrade helper); addFile/addFsDir already drop a stale stat record in place before recording content. Genuine conflicts stay fatal: two divergent real formats, or a stat kind flip (stat:file vs stat:directory).

Companion fix: addFsStat marks #observed only when a stat record actually landed, so a child shard never forwards an entry mergeShard's stat replay would ignore.

Tests

Four regression tests, each verified to fail on the pre-fix code (the forward-direction unit test reproduces the reported crash shape exactly, Conflict for "src/foo.js"):

  • unit: addFsStat yields to a real format addImport recorded for a byte-less target (the production crash)
  • unit: addImport upgrades a stat record recorded before the target was ever resolved
  • unit: divergent stat kinds and divergent real formats still conflict
  • e2e: a module lstatSync'd before it is ever imported upgrades to the real format and round-trips through bundle=load

Full suite: 1390 tests, 0 failures; lint clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_017z3Eb4Udqc5BkdQtt2YA72


Generated by Claude Code

…cord

A stat record's skip keyed off byte records (hashes/sources/resources)
only, but addImport attests a resolve-hook target's format WITHOUT a
byte record when the load hook never fires for it -- stasis-core's own
preload-cached modules are exactly that shape. A --fs capture that then
stat'd such a path (webpack's enhanced-resolve walking
node_modules/@exodus/stasis-core/src/util.js) hit noupsert with
'stat:file' against the attested 'module' and aborted the whole
capture; the reverse order (stat first, then the import attesting the
real format) conflicted the same way.

Route every live-map format merge through one shared rule, upsertFormat:
a real format always takes priority over a 'stat:*' record -- it
replaces a stat record already present and is never displaced by one
arriving later -- while two divergent real formats, or a stat kind flip
(stat:file vs stat:directory), stay fatal conflicts. Used by addFsStat,
addImport, and #mergedFormats (whose local upgrade helper it replaces);
addFile/addFsDir already drop a stale stat record in place before
recording content. addFsStat also marks #observed only when a stat
record actually landed, so a child never forwards an entry the shard
replay would ignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017z3Eb4Udqc5BkdQtt2YA72
@ChALkeR
ChALkeR merged commit 80efdc4 into main Jul 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants