Bug report
Bug description
On Docker Desktop for Mac with VirtioFS file sharing, a host-side unlink() of a file a container has previously looked up is not propagated to the guest. For up to entry_timeout (3600s) the container keeps serving a stale positive dentry, so the path becomes a phantom:
lstat(path) succeeds (stale attrs, e.g. symbolic link, nlink=1)
readdir(parent) does not list the name
readlink(path) / open(path) return EINVAL
Only the macOS→container direction is broken; guest-side changes invalidate synchronously. This is the same cache-coherence family as #7246 (missing host→guest DELETE events), #6989 and #7501, but the EINVAL phantom (with lstat and readdir disagreeing) appears unreported.
The one-hour caching is visible in the VM mount table:
virtiofs0 /run/host_virtiofs/Users virtiofs rw,...,entry_timeout=3600,attr_timeout=3600
Expected: once the host unlinks the path, the container should see it gone (lstat→ENOENT), consistent with readdir; readlink/open must never return EINVAL on a path lstat reports as an existing symlink.
Actual: lstat present + readdir absent + readlink/open EINVAL, for up to 3600s. A container-side rm of the phantom returns ENOENT but flushes the stale entry — the only workaround short of waiting out the TTL.
Real-world impact (Mercurial): hg's lock files (.hg/wlock, .hg/store/lock) are symlinks created and unlinked around every write command — including host-side hg status, which takes the wlock to fix up dirstate. So any host hg plus any container hg within the next hour aborts, with this exact strace signature:
symlinkat(".../.hg/wlock") = -1 EEXIST # stale dentry says "lock exists"
readlinkat(".../.hg/wlock") = -1 EINVAL # phantom
openat(".../.hg/wlock") = -1 EINVAL # hg's readlock fallback also fails
abort: Invalid argument: '.../.hg/wlock'
git is unaffected: .git/index.lock is a regular O_CREAT|O_EXCL file and is never readlinked.
Fix requested: propagate host-side unlink/rename invalidations to the guest (as #7246 asks for events), or expose entry_timeout/attr_timeout as a file-sharing setting — they are fixed at first mount inside the VM and not user-tunable today. Docker's Synchronized file shares (mutagen) sidesteps the shared-cache design entirely.
Related: #7246, #6989, #7501. Also related: #7004 (exclusive flock not enforced on bind mounts) — separate evidence added there.
Reproduction steps
mkdir -p ~/vt && docker run -d --name vt -v ~/vt:/ws debian:12 sleep 600
# container primes the lookup cache while the host churns the same path:
docker exec vt bash -c 'for i in $(seq 1 2000); do stat /ws/lnk >/dev/null 2>&1; done' &
for i in $(seq 1 100); do ln -s target ~/vt/lnk; sleep 0.05; rm ~/vt/lnk; sleep 0.05; done
wait
# phantom — persists for minutes (up to entry_timeout=3600s):
docker exec vt bash -c 'stat /ws/lnk; echo "--- ls -a ---"; ls -a /ws/; echo "--- readlink ---"; readlink /ws/lnk || echo EINVAL'
docker rm -f vt
Observed: stat reports /ws/lnk present, ls -a /ws/ omits it, readlink prints EINVAL.
Platform
macOS
Version information
Client:
Version: 29.4.3
API version: 1.54
Go version: go1.26.2
Git commit: 055a478
Built: Wed May 6 17:06:34 2026
OS/Arch: darwin/arm64
Context: desktop-linux
Server: Docker Desktop 4.74.0 (227015)
Engine:
Version: 29.4.3
API version: 1.54 (minimum version 1.40)
Go version: go1.26.2
Git commit: 56be731
Built: Wed May 6 17:09:05 2026
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: v2.2.3
GitCommit: 77c84241c7cbdd9b4eca2591793e3d4f4317c590
runc:
Version: 1.3.5
GitCommit: v1.3.5-0-g488fc13e
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Client:
Version: 29.4.3
Context: desktop-linux
Debug Mode: false
Plugins:
agent: Docker AI Agent Runner (Docker Inc.)
Version: v1.57.0
Path: /Users/steve/.docker/cli-plugins/docker-agent
ai: Docker AI Agent - Ask Gordon (Docker Inc.)
Version: v1.20.2
Path: /Users/steve/.docker/cli-plugins/docker-ai
buildx: Docker Buildx (Docker Inc.)
Version: v0.33.0-desktop.1
Path: /Users/steve/.docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v5.1.4
Path: /Users/steve/.docker/cli-plugins/docker-compose
debug: Get a shell into any image or container (Docker Inc.)
Version: 0.0.47
Path: /Users/steve/.docker/cli-plugins/docker-debug
desktop: Docker Desktop commands (Docker Inc.)
Version: v0.3.0
Path: /Users/steve/.docker/cli-plugins/docker-desktop
dhi: CLI for managing Docker Hardened Images (Docker Inc.)
Version: v0.0.3
Path: /Users/steve/.docker/cli-plugins/docker-dhi
extension: Manages Docker extensions (Docker Inc.)
Version: v0.2.31
Path: /Users/steve/.docker/cli-plugins/docker-extension
init: Creates Docker-related starter files for your project (Docker Inc.)
Version: v1.4.0
Path: /Users/steve/.docker/cli-plugins/docker-init
mcp: Docker MCP Plugin (Docker Inc.)
Version: v0.42.1
Path: /Users/steve/.docker/cli-plugins/docker-mcp
model: Docker Model Runner (Docker Inc.)
Version: v1.1.37
Path: /Users/steve/.docker/cli-plugins/docker-model
offload: Docker Offload (Docker Inc.)
Version: v0.5.89
Path: /Users/steve/.docker/cli-plugins/docker-offload
pass: Docker Pass Secrets Manager Plugin (beta) (Docker Inc.)
Version: v0.0.27
Path: /Users/steve/.docker/cli-plugins/docker-pass
sandbox: Docker Sandbox (Docker Inc.)
Version: v0.12.0
Path: /Users/steve/.docker/cli-plugins/docker-sandbox
sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.)
Version: 0.6.0
Path: /Users/steve/.docker/cli-plugins/docker-sbom
scout: Docker Scout (Docker Inc.)
Version: v1.21.0
Path: /Users/steve/.docker/cli-plugins/docker-scout
Server:
Containers: 14
Running: 8
Paused: 0
Stopped: 6
Images: 17
Server Version: 29.4.3
Storage Driver: overlayfs
driver-type: io.containerd.snapshotter.v1
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
CDI spec directories:
/etc/cdi
/var/run/cdi
Discovered Devices:
cdi: docker.com/gpu=webgpu
Swarm: inactive
Runtimes: runc io.containerd.runc.v2
Default Runtime: runc
Init Binary: docker-init
containerd version: 77c84241c7cbdd9b4eca2591793e3d4f4317c590
runc version: v1.3.5-0-g488fc13e
init version: de40ad0
Security Options:
seccomp
Profile: builtin
cgroupns
Kernel Version: 6.12.76-linuxkit
Operating System: Docker Desktop
OSType: linux
Architecture: aarch64
CPUs: 12
Total Memory: 7.75GiB
Name: docker-desktop
ID: 2b883330-88ce-45da-a307-018bab4cc02b
Docker Root Dir: /var/lib/docker
Debug Mode: false
HTTP Proxy: http.docker.internal:3128
HTTPS Proxy: http.docker.internal:3128
No Proxy: hubproxy.docker.internal
Labels:
com.docker.desktop.address=unix:///Users/steve/Library/Containers/com.docker.docker/Data/docker-cli.sock
Experimental: false
Insecure Registries:
hubproxy.docker.internal:5555
::1/128
127.0.0.0/8
Live Restore Enabled: false
Firewall Backend: iptables
Diagnostics ID
7EA2D421-16F4-4197-A23C-EE7F235D836F/20260713062418
Bug report
Bug description
On Docker Desktop for Mac with VirtioFS file sharing, a host-side
unlink()of a file a container has previously looked up is not propagated to the guest. For up toentry_timeout(3600s) the container keeps serving a stale positive dentry, so the path becomes a phantom:lstat(path)succeeds (stale attrs, e.g.symbolic link,nlink=1)readdir(parent)does not list the namereadlink(path)/open(path)return EINVALOnly the macOS→container direction is broken; guest-side changes invalidate synchronously. This is the same cache-coherence family as #7246 (missing host→guest DELETE events), #6989 and #7501, but the EINVAL phantom (with
lstatandreaddirdisagreeing) appears unreported.The one-hour caching is visible in the VM mount table:
Expected: once the host unlinks the path, the container should see it gone (
lstat→ENOENT), consistent withreaddir;readlink/openmust never return EINVAL on a pathlstatreports as an existing symlink.Actual:
lstatpresent +readdirabsent +readlink/openEINVAL, for up to 3600s. A container-sidermof the phantom returns ENOENT but flushes the stale entry — the only workaround short of waiting out the TTL.Real-world impact (Mercurial): hg's lock files (
.hg/wlock,.hg/store/lock) are symlinks created and unlinked around every write command — including host-sidehg status, which takes the wlock to fix up dirstate. So any hosthgplus any containerhgwithin the next hour aborts, with this exact strace signature:git is unaffected:
.git/index.lockis a regularO_CREAT|O_EXCLfile and is never readlinked.Fix requested: propagate host-side unlink/rename invalidations to the guest (as #7246 asks for events), or expose
entry_timeout/attr_timeoutas a file-sharing setting — they are fixed at first mount inside the VM and not user-tunable today. Docker's Synchronized file shares (mutagen) sidesteps the shared-cache design entirely.Related: #7246, #6989, #7501. Also related: #7004 (exclusive
flocknot enforced on bind mounts) — separate evidence added there.Reproduction steps
Observed:
statreports/ws/lnkpresent,ls -a /ws/omits it,readlinkprintsEINVAL.Platform
macOS
Version information
Diagnostics ID
7EA2D421-16F4-4197-A23C-EE7F235D836F/20260713062418