Skip to content

Windows packaging false-positive: Cannot read file .babelrc due to: file changed between metadata collection and stream open #280

@spark323

Description

@spark323

Summary

On Windows, OSLS packaging can fail with the following error:

Cannot read file .babelrc due to: file changed between metadata collection and stream open

After investigating, this appears to be a false positive in the streaming zip safety check.

The file is not actually changing. The failure seems to be caused by different dev values returned by fs.stat(path) and fileHandle.stat() for the same unchanged file.

Environment

  • OS: Windows
  • Shell: PowerShell
  • Node runtime: Node.js 24.x
  • OSLS version where the issue occurs: 3.73.0
  • Project package dependency:
{
  "osls": "^3.61.1"
}

Because the dependency range is loose, a fresh install resolved OSLS to 3.73.0.

Error

During deploy/package, packaging fails with:

Cannot read file .babelrc due to: file changed between metadata collection and stream open

The referenced file is tiny and stable:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript"
  ]
}

Investigation

The error appears to come from:

lib/plugins/package/lib/zip-service.js

Specifically:

const assertSameFile = (entry, currentStat) => {
  const sameIdentity =
    entry.stat.dev === currentStat.dev &&
    entry.stat.ino === currentStat.ino;

  const sameContentMetadata =
    entry.stat.size === currentStat.size &&
    entry.stat.mtimeMs === currentStat.mtimeMs;

  if (!sameIdentity || !sameContentMetadata) {
    throw new Error('file changed between metadata collection and stream open');
  }
};

I reproduced the metadata mismatch directly with Node:

const fs = require('fs/promises');

(async () => {
  const p = '.babelrc';

  const a = await fs.stat(p);
  const h = await fs.open(p, 'r');
  const b = await h.stat();

  await h.close();

  console.log(JSON.stringify({
    path: {
      dev: a.dev,
      ino: a.ino,
      size: a.size,
      mtimeMs: a.mtimeMs,
    },
    handle: {
      dev: b.dev,
      ino: b.ino,
      size: b.size,
      mtimeMs: b.mtimeMs,
    },
    same:
      a.dev === b.dev &&
      a.ino === b.ino &&
      a.size === b.size &&
      a.mtimeMs === b.mtimeMs,
  }, null, 2));
})();

Output:

{
  "path": {
    "dev": 0,
    "ino": 562949955664956,
    "size": 83,
    "mtimeMs": 1780191134753.168
  },
  "handle": {
    "dev": 1587983220,
    "ino": 562949955664956,
    "size": 83,
    "mtimeMs": 1780191134753.168
  },
  "same": false
}

The file identity/content metadata is otherwise stable:

  • ino matches
  • size matches
  • mtimeMs matches
  • only dev differs

So the file was not modified between metadata collection and stream open.

Why this looks related to recent packaging changes

The relevant behavior appears to have been introduced around the streaming zip packaging work.

The v3.68.0 release notes include:

Stream zip packaging and artifact hashing to fix OOM

The v3.69.0 release notes include a related follow-up:

Close zip stream file handle on simulated failure

Release notes:

The streaming zip implementation makes sense, but the equality check appears to be too strict for Windows/Node filesystem metadata.

Expected Behavior

Packaging should not fail when the file has not actually changed.

If only dev differs between fs.stat(path) and fileHandle.stat() on Windows, OSLS should not report the file as changed.

Actual Behavior

Packaging fails with:

Cannot read file .babelrc due to: file changed between metadata collection and stream open

Suggested Fix

Possible approaches:

  1. Do not compare stat.dev on Windows.
  2. Compare only reliable content metadata for this safety check, such as:
    • ino
    • size
    • mtimeMs
  3. Treat dev mismatch as non-fatal when process.platform === 'win32'.

For example:

const sameIdentity =
  process.platform === 'win32'
    ? entry.stat.ino === currentStat.ino
    : entry.stat.dev === currentStat.dev &&
      entry.stat.ino === currentStat.ino;

Or, more conservatively, only use dev when both values are non-zero and equal semantics are known to be reliable.

const sameDevice =
  entry.stat.dev === 0 ||
  currentStat.dev === 0 ||
  entry.stat.dev === currentStat.dev;

const sameIdentity =
  sameDevice &&
  entry.stat.ino === currentStat.ino;

Workaround

Pinning OSLS to an older version that does not fail during packaging avoids the issue.
However, this is not ideal because projects using a semver range like:

{
  "osls": "^3.61.1"
}

can resolve to newer versions and start failing unexpectedly on Windows.

Disclosure

This issue was drafted with AI assistance, based on my own investigation, reproduction steps, and observed results.

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions