Skip to content

change event is fired for directories and files that were created shortly before startTime, when startTime is set #295

@Rob--W

Description

@Rob--W

Have you used AI?

No

Bug Description

When Watchpack is initialized with startTime: Date.now(), it fires the change event for directories / files that were created shortly before.

When startTime is not set, the change event is not fired as desired (fixed with test coverage in #150).

README.md documents startTime as being an optional number, and "Watching can be started in the past. This way watching can start after file reading.". The observed behavior of getting unwanted events is surprising.

Moreover, the source shows that FS_ACCURACY (up to two seconds!) is added to the start time. Any file/directory older than this time will be flagged as changed. This is not documented and surprising (and contributing to this bug):

const safeTime = initial ? Math.min(now, birthtime) + FS_ACCURACY : now;

The above source also shows the use of birthtime/btime instead of ctime. This is surprising and not documented in README.
I tried using fs.utimes with a past date as a work-around. This works on macOS but failed on Linux and Windows, because fs.utimes updates mtime but not btime. The use of btime appears to be introduced in #183. The work-around works in watchpack@2.0.1 (which is before 2.1.0 that includes #183).

Link to Minimal Reproduction and step to reproduce

npm install watchpack ( tested with latest version watchpack@2.5.1 and Node 25.9.0)
Save following file as test.mjs and run node test.mjs:

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import Watchpack from "watchpack";

const base = path.join(os.tmpdir(), "watchpack_repro");
await fs.rm(base, { recursive: true }).catch(() => {}); // ignore ENOENT
await fs.mkdir(base);
await fs.mkdir(path.join(base, "subdir"));

const dateInPast = Date.now() / 1000 - 10; // 10 seconds older should be plenty
// await fs.utimes(path.join(base, 'subdir'), dateInPast, dateInPast);
// ^ Works on macOS (changes btime), not on Linux or Windows. Also works in watchpack<=2.0.1

const w = new Watchpack({});
w.on("change", console.log);
w.watch({
  directories: [base],
  missing: [],
  startTime: Date.now(),
});
console.log("Waiting 2 seconds. Should not log anything.");
await new Promise(r => setTimeout(r, 2000));
w.close();

Expected Behavior

Should not log change events for files/directories that were created shortly before Watchpack is initialized.

Actual Behavior

Logs a change event for the subdirectory that was created immediately before:

/tmp/watchpack_repro/subdir 1777218474762 scan (dir)

(the minimal STR uses a directory, but this also reproduces for files)

Environment

Node: v25.9.0 (and also tested v20.20.2)
Linux: Ubuntu 24.04.4 LTS
macOS: 15.7.5

Is this a regression?

No

Last Working Version

No response

Additional Context

The fs.utimes work-around to get the test to pass works on macOS because it apparently changes the btime as well:

$ mkdir d;node -e 'require("fs/promises").utimes("d", 1234, 5678)';stat -x d
...
Access: Thu Jan  1 01:20:34 1970
Modify: Thu Jan  1 02:34:38 1970
Change: Sun Apr 26 18:19:50 2026
 Birth: Thu Jan  1 02:34:38 1970

On Linux, the Birth time is not modified when I run the same command.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions