You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have tried with the latest version of Docker Desktop
I have tried disabling enabled experimental features
I have uploaded Diagnostics
Diagnostics ID: none — I'm not able to share a bundle from this machine. The repro below is self-contained and reproduces on a stock python:3-slim image against any bind-mounted host directory, so it shouldn't need one. Happy to run specific commands and report the output instead.
Expected behavior
After mkdir(path) returns, statx(path, STATX_BTIME) should describe that path, as statx(path, STATX_BASIC_STATS) and stat(path) both already do.
Actual behavior
On a VirtioFS bind mount, statx() returns ENOENT for a just-created path whenever STATX_BTIME (0x800) is in the requested mask. Removing that one bit makes the identical call succeed on the identical path. The entry is really on disk throughout — readdir lists it, and open/access succeed.
It is create-scoped and time-bounded, not a permanent negative cache: the same masked call succeeds once the path is a few milliseconds old.
arch=aarch64 __NR_statx=291
mkdir(), then statx() the same path:
mask=STATX_BASIC_STATS (0x7ff) 0/20 ENOENT
mask=STATX_BTIME (0x800) 20/20 ENOENT
mask=basic|btime (0xfff) 20/20 ENOENT
mask=STATX_BTIME, after 5ms sleep 0/20 ENOENT
os.stat() (newfstatat, no btime) for comparison:
os.stat() 0/20 ENOENT
Reproduce
Stock python:3-slim, no Node, no project code. Any bind-mounted host directory:
mkdir -p /tmp/statxbug &&cd /tmp/statxbug
cat > probe.py <<'EOF'import ctypes, ctypes.util, os, platform, timeNR = {"aarch64": 291, "x86_64": 332}[platform.machine()]AT_FDCWD, ENOENT = -100, 2BASIC, BTIME = 0x7FF, 0x800libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)buf = ctypes.create_string_buffer(256)def statx(path, mask): ctypes.set_errno(0) rc = libc.syscall( ctypes.c_long(NR), ctypes.c_int(AT_FDCWD), ctypes.c_char_p(path.encode()), ctypes.c_int(0), ctypes.c_uint(mask), ctypes.byref(buf), ) if rc != 0: raise OSError(ctypes.get_errno(), "statx")def trial(label, mask, delay=0.0): fail = 0 for i in range(20): p = "/vol/probe-%x-%d" % (mask, i) os.mkdir(p) if delay: time.sleep(delay) try: statx(p, mask) except OSError as e: fail += e.errno == ENOENT os.rmdir(p) print(" %-44s %2d/20 ENOENT" % (label, fail))print("arch=%s __NR_statx=%d" % (platform.machine(), NR))print("mkdir(), then statx() the same path:")trial("mask=STATX_BASIC_STATS (0x7ff)", BASIC)trial("mask=STATX_BTIME (0x800)", BTIME)trial("mask=basic|btime (0xfff)", BASIC | BTIME)trial("mask=STATX_BTIME, after 5ms sleep", BTIME, 0.005)print("os.stat() (newfstatat, no btime) for comparison:")fail = 0for i in range(20): p = "/vol/plain-%d" % i os.mkdir(p) try: os.stat(p) except FileNotFoundError: fail += 1 os.rmdir(p)print(" %-44s %2d/20 ENOENT" % ("os.stat()", fail))EOF
docker run --rm -v "$PWD:/vol" python:3-slim python3 /vol/probe.py
So asking for btime is exactly what routes a request to FUSE_STATX, which is the opcode that answers ENOENT here. Everything that stays within STATX_BASIC_STATS takes FUSE_GETATTR and is fine.
libuv requests btime, so every fs.stat() in Node on a VirtioFS bind hits this path. In one Node process on one mount, fresh unique path per iteration:
Node call
result
statSync / lstatSync
10/10 ENOENT
accessSync, existsSync
0/10
readdirSync, opendirSync
0/10
openSync, readFileSync
0/10
Downstream this reads as tooling being broken rather than as a filesystem fault. Two we hit: Storybook's mkdirp stats a directory it just created and dies with ENOTDIR: not a directory, mkdir …/node_modules/.cache/storybook/…/public, and Vitest writes .vite-temp/vitest.config.ts.timestamp-*.mjs and then has the ESM resolver stat it, dying with ERR_MODULE_NOT_FOUND before a single test runs. Both look like broken configs and are not.
A shell mkdir d && stat d does not reproduce it, because process spawn costs more than the window — which makes this awkward to catch by hand.
Information
Reproduces on every run, 20/20, on a fresh unique path each iteration.
Recovery time varies with system load — a repeat run showed 4/20 rather than 0/20 for the 5 ms case. The mask result itself is stable across every run: 0/20 for 0x7ff, 20/20 for 0x800.
Not observed on an ext4 named volume or on the container overlay — 0/20 on both, same probe.
No workaround from userland that we can see: Node exposes no way to ask libuv to drop btime.
python:3-slimimage against any bind-mounted host directory, so it shouldn't need one. Happy to run specific commands and report the output instead.Expected behavior
After
mkdir(path)returns,statx(path, STATX_BTIME)should describe that path, asstatx(path, STATX_BASIC_STATS)andstat(path)both already do.Actual behavior
On a VirtioFS bind mount,
statx()returns ENOENT for a just-created path wheneverSTATX_BTIME(0x800) is in the requested mask. Removing that one bit makes the identical call succeed on the identical path. The entry is really on disk throughout —readdirlists it, andopen/accesssucceed.It is create-scoped and time-bounded, not a permanent negative cache: the same masked call succeeds once the path is a few milliseconds old.
Reproduce
Stock
python:3-slim, no Node, no project code. Any bind-mounted host directory:The mount under test:
Why this reaches ordinary users
The kernel picks the FUSE opcode from the requested mask. In
fuse_update_get_attr:So asking for btime is exactly what routes a request to
FUSE_STATX, which is the opcode that answers ENOENT here. Everything that stays withinSTATX_BASIC_STATStakesFUSE_GETATTRand is fine.libuv requests btime, so every
fs.stat()in Node on a VirtioFS bind hits this path. In one Node process on one mount, fresh unique path per iteration:statSync/lstatSyncaccessSync,existsSyncreaddirSync,opendirSyncopenSync,readFileSyncDownstream this reads as tooling being broken rather than as a filesystem fault. Two we hit: Storybook's
mkdirpstats a directory it just created and dies withENOTDIR: not a directory, mkdir …/node_modules/.cache/storybook/…/public, and Vitest writes.vite-temp/vitest.config.ts.timestamp-*.mjsand then has the ESM resolver stat it, dying withERR_MODULE_NOT_FOUNDbefore a single test runs. Both look like broken configs and are not.A shell
mkdir d && stat ddoes not reproduce it, because process spawn costs more than the window — which makes this awkward to catch by hand.Information
0x7ff, 20/20 for0x800.open/copyfilefailures reported there do not reproduce for us, and that issue predates FUSE statx support (Linux 6.6), so it may well be unrelated.docker version
Host
7.0.12-linuxkit #1 SMP PREEMPT Wed Aug 12 20:18:49 UTC 2026 aarch64