Skip to content

fix(windows): skip unreliable dev comparison in fs-safe openVerifiedLocalFile#25708

Closed
kevinWangSheng wants to merge 1 commit intoopenclaw:mainfrom
kevinWangSheng:fix/windows-fs-safe-dev-ino-mismatch
Closed

fix(windows): skip unreliable dev comparison in fs-safe openVerifiedLocalFile#25708
kevinWangSheng wants to merge 1 commit intoopenclaw:mainfrom
kevinWangSheng:fix/windows-fs-safe-dev-ino-mismatch

Conversation

@kevinWangSheng
Copy link
Copy Markdown
Contributor

@kevinWangSheng kevinWangSheng commented Feb 24, 2026

Problem

On Windows, inbound images fail with Local media path is not safe to read / path changed during read. The root cause is a false-positive in openVerifiedLocalFile where handle.stat() and fs.lstat() return different dev values on Windows, causing the path-mismatch check to throw even though the file is stable and not a symlink.

Root Cause

In src/infra/fs-safe.ts:

const [stat, lstat] = await Promise.all([handle.stat(), fs.lstat(filePath)]);
if (stat.ino !== lstat.ino || stat.dev !== lstat.dev) {
  throw new SafeOpenError("path-mismatch", "path changed during read");
}

On Windows, handle.stat().dev differs from fs.lstat().dev even for the same file. This triggers the error even though the file is stable and not a symlink.

Fix

Introduce a statsMatch() helper that:

  • Always compares inode (ino) values
  • Skips device ID (dev) comparison on Windows where it's unreliable
  • Maintains full comparison on Unix platforms
function statsMatch(stat1: Stats, stat2: Stats): boolean {
  if (stat1.ino !== stat2.ino) {
    return false;
  }
  // On Windows, dev values are unreliable across different stat sources
  if (process.platform !== "win32" && stat1.dev !== stat2.dev) {
    return false;
  }
  return true;
}

Testing

  • All 7 existing tests pass
  • pnpm build passes
  • pnpm lint passes

Fixes #25699

Greptile Summary

Fixes Windows media path verification by introducing a statsMatch() helper that skips unreliable device ID (dev) comparison on Windows while maintaining inode (ino) verification. The fix resolves false-positive "path changed during read" errors when handle.stat() and fs.lstat() return different device IDs for the same file on Windows.

  • Introduced statsMatch() helper function with platform-specific stat comparison logic
  • Maintains full dev + ino verification on Unix platforms
  • Skips dev comparison on Windows where it's known to be unreliable
  • Tests pass and the fix is well-documented in code comments

The approach is reasonable for Windows compatibility, though consider aligning with the existing sameFileIdentity() pattern in src/infra/safe-open-sync.ts which uses a more targeted fallback (checking if either dev is 0 rather than unconditionally skipping dev comparison).

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - it fixes a real Windows bug without weakening security
  • The fix correctly addresses a known Windows platform limitation where device IDs are unreliable. Inode comparison remains intact for file identity verification, symlink checks are preserved, and all existing tests pass. The only consideration is alignment with the existing sameFileIdentity() pattern in safe-open-sync.ts, but the current approach is valid and well-documented.
  • No files require special attention

Last reviewed commit: f59ed21

Context used:

  • Context from dashboard - CLAUDE.md (source)

…ocalFile

On Windows, device IDs (dev) returned by handle.stat() and fs.lstat()
may differ even for the same file, causing false-positive 'path-mismatch'
errors when reading local media files.

This fix introduces a statsMatch() helper that:
- Always compares inode (ino) values
- Skips device ID (dev) comparison on Windows where it's unreliable
- Maintains full comparison on Unix platforms

Fixes openclaw#25699
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +49 to +58
function statsMatch(stat1: Stats, stat2: Stats): boolean {
if (stat1.ino !== stat2.ino) {
return false;
}
// On Windows, dev values are unreliable across different stat sources
if (process.platform !== "win32" && stat1.dev !== stat2.dev) {
return false;
}
return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing src/infra/safe-open-sync.ts:sameFileIdentity() uses a more nuanced approach: it checks if either dev value is 0 on Windows before falling back to ino-only comparison. Your implementation unconditionally skips dev comparison on all Windows systems. Consider aligning with the existing pattern:

Suggested change
function statsMatch(stat1: Stats, stat2: Stats): boolean {
if (stat1.ino !== stat2.ino) {
return false;
}
// On Windows, dev values are unreliable across different stat sources
if (process.platform !== "win32" && stat1.dev !== stat2.dev) {
return false;
}
return true;
}
function statsMatch(stat1: Stats, stat2: Stats): boolean {
if (stat1.ino !== stat2.ino) {
return false;
}
// On Windows, dev values are unreliable across different stat sources
const devMatch =
stat1.dev === stat2.dev ||
(process.platform === "win32" && (stat1.dev === 0 || stat2.dev === 0));
if (!devMatch) {
return false;
}
return true;
}

This provides better security on Windows systems where both dev values are valid and non-zero.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/fs-safe.ts
Line: 49-58

Comment:
The existing `src/infra/safe-open-sync.ts:sameFileIdentity()` uses a more nuanced approach: it checks if *either* `dev` value is 0 on Windows before falling back to ino-only comparison. Your implementation unconditionally skips `dev` comparison on all Windows systems. Consider aligning with the existing pattern:

```suggestion
function statsMatch(stat1: Stats, stat2: Stats): boolean {
  if (stat1.ino !== stat2.ino) {
    return false;
  }
  // On Windows, dev values are unreliable across different stat sources
  const devMatch =
    stat1.dev === stat2.dev ||
    (process.platform === "win32" && (stat1.dev === 0 || stat2.dev === 0));
  if (!devMatch) {
    return false;
  }
  return true;
}
```

This provides better security on Windows systems where both `dev` values are valid and non-zero.

How can I resolve this? If you propose a fix, please make it concise.

@steipete
Copy link
Copy Markdown
Contributor

Superseded by #25917, now merged to main (merge commit 943b8f171a78fda44ba065dcec29da27b8c072c0).

What landed from this line of work:

  • shared file-identity helper used by both async and sync safe-open paths
  • Windows dev=0 fallback parity (instead of async/sync drift)
  • strict dev checking preserved when both sides are non-zero
  • focused regression tests (src/infra/file-identity.test.ts) to lock behavior

Thank you for surfacing and narrowing the root cause quickly; this directly informed the final landed fix.

@steipete steipete closed this Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: image reads fail with "Local media path is not safe to read" due to dev/ino mismatch in fs-safe openVerifiedLocalFile

2 participants