Repository navigation
fix(store): on Windows the path comparison reads a path's volume through a handle — on Node 22.16.x (libuv 1.49.2) a path through a share of the workspace compared as outside it - #165
Merged
Conversation
…ugh a handle — on Node 22.16.x (libuv 1.49.2) a path through a share of the workspace compared as outside it server/store/paths.mjs placed a path reached through a share by a file id it shares with a directory of the path it is checked against, and judged one nothing places by its volume, both read with statSync. libuv answers a stat by the path's name (GetFileInformationByName, Windows 11 24H2 and Server 2025) unless the path is reached through a share or its last name is a reparse point, which it opens (FILE_FS_VOLUME_INFORMATION: the serial). libuv 1.49.0 to 1.50.x declared FILE_STAT_BASIC_INFORMATION with FileId128 before VolumeSerialNumber (fixed in 1.51.0), so a stat by name reported the upper half of the 128-bit file id, 0 on NTFS. A local path and its share then had no file id in common and their volumes never matched: a directory export to \\myhost\ws\exports\new with the workspace at C:\ws was written into the workspace. On Windows a file id and a volume are now read through a handle (openSync, fstatSync, closeSync), which reports the serial on every libuv, as #163 reads a directory's volume in users-file-place.mjs. Every handle is closed before the comparison goes on. server/fixtures/ntfs.mjs models both libuvs (fastDev: 0n for 1.49.2, null for 1.51.0's 64-bit serial by name; a path through a share or ending in a link or reparse point is opened) and returns openSync, fstatSync, closeSync and opened(). Its header no longer says a local stat may report all 64 bits on every libuv. The new test in server/test-store-paths.mjs loops both models. At the parent commit it fails on 1.49.2 (the export through \\myhost\ws was written). Tests: 1495 -> 1496. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…chain this follow-up's Tests pair from develop's 1519 — the develop merge cd24602 dropped #163's entry (17 lines, now byte-for-byte develop's again, below this entry) and left this entry's pair at 1495 → 1496, which test-doc-test-totals matched against #163's journey pair from 1495; 1519 → 1520 is the measured npm test total Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPGnqVJ24goSVtbKpdkLJ
This was referenced Oct 15, 2026
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.
Problem
libuv 1.49.0–1.50.x (Node 22.16.x ships 1.49.2) declares
FILE_STAT_BASIC_INFORMATIONwithFileId128beforeVolumeSerialNumber, which is the wrong order. libuv 1.51.0 fixed it ("win: fix order of FILE_STAT_BASIC_INFORMATION struct fields"). So a stat by name reportsst_dev= the upper 64 bits of the 128-bit file id, which is 0 on NTFS. libuv stats by name (GetFileInformationByName, Windows 11 24H2 / Server 2025) for any local path whose last name is not a reparse point. A path through a share, or one ending in a junction or symlink, is opened instead and reports the real 32-bit serial.server/store/paths.mjs(from #161) read both the file ids used for placement and the volume used for the "through a share on beside's volume" rule withstatSync. On 1.49.2 a local path and a share of it then had no file id in common, and a local volume never matched a share's. The share-volume rule could fail open.Reproduced on Linux through the injected win32 host (before the fix)
On 1.49.2:
\\myhost\ws\exports\newwas written into the workspace.-walthrough a share.Fix
On Windows,
comparedPathreads a file id and a volume through a handle:openSync(p, 'r')+fstatSync(fd, { bigint: true })+closeSync, with the handle closed infinally. This is the same approach #163 (fdb1603) uses inusers-file-place.mjs. libuv'sfs__opensetsFILE_FLAG_BACKUP_SEMANTICS, so directories open, andfs__stat_handlereports the serial on every libuv. A path that doesn't open is passed over, the same way a path that didn't stat was.comparedPathtakesopenSync,fstatSyncandcloseSyncin place ofstatSync.Fixture
server/fixtures/ntfs.mjsgets the same model as #163'smount-fs.mjs, which isn't on develop yet:fastDevis the device a stat by name reports.null(the default) gives all 64 bits of the serial, as 1.51.0 reads it;0ngives what 1.49.2 reports.openSync,fstatSync,closeSyncandopened().Tests
server/test-store-paths.mjsthat loops libuv 1.51.0 and 1.49.2. It covers the export through\\myhost\wsand into a workspace named\\myhost\ws, the in-place detection, backup's-wal, purge-org placing under and beside the root, the share-volume rule, another volume, and every handle closed.a directory export through \\myhost\ws into C:\ws is refused, expected'inside', gotnull.hostOf,handlesOf;flippingroutes a handle to the volume it was opened on).Gates (local, Windows 11, Node 22.16.0)
npm run lint: 0 errors. The 153 warnings are unchanged, with 0 in the changed files (develop's versions also had 0).test:goldenandtest:golden:compile✓;vendor-manifest:check✓ (notools/libtouched);node tools/sync-spec.mjs --check✓;tools/test-doc-test-totals.mjs✓.server/test-store-paths.mjs19/19;tools/test-journey-scope.mjs✓.npm testlocally has 5 failures (test-auth-local,test-authz,test-brand-shell,test-smoke,test-studio-bundle). They fail the same way with develop's versions of the changed files on this machine, so they're local-environment failures that already exist. CI is the gate for Node 22 (22.22) and the Windows legs.Not in this PR
crawlScopeRefusalintools/lib/journey.mjs(paths.mjs's "twin") still places bystatSync'sdev & 0xffffffff. On 1.49.2 it fails closed: a local path beside a workspace named through a share is refused, not let through. This is a follow-up because it touchestools/lib, so it needs the vendor-manifest + CHANGELOG export rule.Merge order with #163: both claim
1495 → 1496, so whichever lands second re-chains its Tests pair.🤖 Generated with Claude Code