Repository navigation
macOS bind-mount root intermittently reports UID/GID 0:0 via lstat() / newfstatat() #628
Description
Bug report
Description
On Docker Desktop for macOS, the root directory of a bind mount intermittently reports UID/GID 0:0 through lstat() / newfstatat(), despite normally being owned by UID/GID 1000:1000.
This reproduces using plain docker run with a stock node image, and reproduces with both VirtioFS and gRPC FUSE file sharing while using Apple Virtualization Framework.
Child directories beneath the bind-mount root continue to report the expected ownership. This causes Git repositories mounted directly at the bind-mount root to intermittently fail with fatal: detected dubious ownership.
Environment
- macOS: macOS 26.6.2 (build 25G83)
- Architecture:
arm64 - Docker Desktop:
v4.88.0 - Virtual Machine Manager: Apple Virtualization Framework
Minimal reproduction
On the macOS host, from any Git repository:
docker run --rm -it \
-v "$PWD:/workspaces/test" \
-w /workspaces/test \
-u node \
node \
bashInside the container, run Git repeatedly as the node user (UID 1000):
for i in $(seq 1 1000); do
git status --porcelain >/dev/null 2>/tmp/git.err || {
echo "FAILED on iteration $i"
cat /tmp/git.err
break
}
doneThe failure generally occurs within a small number of iterations:
FAILED on iteration 2
fatal: detected dubious ownership in repository at '/workspaces/test'
Syscall evidence
Tracing a failed Git invocation shows the bind-mount root intermittently reported as owned by root:
newfstatat(
AT_FDCWD,
"/workspaces/test",
{
...
st_uid=0,
st_gid=0,
...
},
AT_SYMLINK_NOFOLLOW
) = 0
In successful invocations, the same syscall on the same path returns:
st_uid=1000,
st_gid=1000
Meanwhile a directory below the mount root, such as .git, continues to report the correct ownership even during a failing invocation:
/workspaces/test uid=0 gid=0
/workspaces/test/.git uid=1000 gid=1000
Not specific to Git
A small C wrapper calling lstat() before executing Git has also observed the incorrect metadata before Git starts:
struct stat st;
lstat("/workspaces/test", &st);
fprintf(stderr, "euid=%u uid=%u gid=%u\n",
geteuid(), st.st_uid, st.st_gid);Example failing result:
euid=1000 uid=0 gid=0
Therefore Git itself is not causing the incorrect ownership metadata.
statx() differs
GNU stat uses statx() and consistently reports the expected UID/GID:
1000:1000
Tracing it shows:
statx(
AT_FDCWD,
"/workspaces/test",
AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT,
STATX_UID|STATX_GID,
{
...
stx_attributes=STATX_ATTR_MOUNT_ROOT,
...
}
) = 0
Tens of thousands of repeated statx()-based lookups did not reproduce the incorrect 0:0 ownership.
Mount-root-specific behavior
The problem appears specific to the bind mount root.
If another Git repository is initialized one level below it:
/workspaces/test ← bind mount root
└── nested ← nested Git worktree
└── .git
then:
for i in $(seq 1 1000); do
git -C nested status --porcelain >/dev/null || {
echo "FAILED $i"
break
}
donedoes not reproduce the issue.
The repository located directly at /workspaces/test continues to fail within a few iterations.
Expected behavior
Ownership metadata for the bind-mount root should be stable and consistent across metadata APIs.
If the directory is owned by UID/GID 1000:1000, both statx() and newfstatat() / lstat() should consistently report:
1000:1000
Actual behavior
newfstatat() intermittently reports the mount root as:
0:0
while:
statx()reports1000:1000- child directories report
1000:1000 - the effective UID of the process remains
1000
This causes applications that perform ownership-based security checks using lstat() to behave incorrectly (like git).
Workaround
Mounting the parent directory instead:
/workspaces ← bind mount root
└── test ← Git repository
avoids the Git symptom because the Git worktree is no longer itself the bind mount root.
Possibly related: #508
Platform
macOS
Version information
Client:
Version: 29.7.2
API version: 1.55
Go version: go1.26.5
Git commit: a7dcaa6
Built: Wed Aug 5 18:27:50 2026
OS/Arch: darwin/arm64
Context: desktop-linux
Server: Docker Desktop 4.88.0 (237115)
Engine:
Version: 29.7.2
API version: 1.55 (minimum version 1.40)
Go version: go1.26.5
Git commit: 6a43e3d
Built: Wed Aug 5 18:28:35 2026
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: v2.3.3
GitCommit: aad11006b869517fcd3009450b6f82da282e1a9b
runc:
Version: 1.4.3
GitCommit: v1.4.3-0-gbb14dabe
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Client:
Version: 29.7.2
Context: desktop-linux
Debug Mode: false
Plugins:
agent: Docker AI Agent Runner (Docker Inc.)
Version: v1.124.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-agent
ai: Docker AI Agent - Ask Gordon (Docker Inc.)
Version: v1.30.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-ai
buildx: Docker Buildx (Docker Inc.)
Version: v0.36.1-desktop.1
Path: /Users/mjswensen/.docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v5.4.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-compose
debug: Get a shell into any image or container (Docker Inc.)
Version: 0.0.47
Path: /Users/mjswensen/.docker/cli-plugins/docker-debug
desktop: Docker Desktop commands (Docker Inc.)
Version: v0.4.3
Path: /Users/mjswensen/.docker/cli-plugins/docker-desktop
dhi: CLI for managing Docker Hardened Images (Docker Inc.)
Version: v0.0.7
Path: /Users/mjswensen/.docker/cli-plugins/docker-dhi
extension: Manages Docker extensions (Docker Inc.)
Version: v0.2.31
Path: /Users/mjswensen/.docker/cli-plugins/docker-extension
init: Creates Docker-related starter files for your project (Docker Inc.)
Version: v1.4.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-init
mcp: Docker MCP Plugin (Docker Inc.)
Version: v0.43.3
Path: /Users/mjswensen/.docker/cli-plugins/docker-mcp
model: Docker Model Runner (Docker Inc.)
Version: v1.2.6
Path: /Users/mjswensen/.docker/cli-plugins/docker-model
offload: Docker Offload (Docker Inc.)
Version: v0.6.13
Path: /Users/mjswensen/.docker/cli-plugins/docker-offload
pass: Docker Pass Secrets Manager Plugin (beta) (Docker Inc.)
Version: v0.2.1
Path: /Users/mjswensen/.docker/cli-plugins/docker-pass
sandbox: "docker sandbox" is deprecated, use Docker Sandboxes instead (Docker Inc.)
Version: v0.13.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-sandbox
scout: Docker Scout (Docker Inc.)
Version: v1.24.0
Path: /Users/mjswensen/.docker/cli-plugins/docker-scout
Server:
Containers: 17
Running: 0
Paused: 0
Stopped: 17
Images: 67
Server Version: 29.7.2
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: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: aad11006b869517fcd3009450b6f82da282e1a9b
runc version: v1.4.3-0-gbb14dabe
init version: de40ad0
Security Options:
seccomp
Profile: builtin
cgroupns
Kernel Version: 7.0.12-linuxkit
Operating System: Docker Desktop
OSType: linux
Architecture: aarch64
CPUs: 10
Total Memory: 7.748GiB
Name: docker-desktop
ID: 5ad4520f-9680-40b8-ba0a-1ae2dd24f5d5
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/mjswensen/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: iptablesDiagnostics ID
No response
Activity
radutopala commentedon Aug 27, 2026
Independent reproduction on 4.88.1, so this is not fixed in the current release.
Environment
linux/arm64Three things that may help narrow it down
1. It is bursty, not uniformly random. It stays broken for a sustained window, then goes quiet. Two samples minutes apart on the same path:
So a reproduction attempt can look completely clean for minutes. Anyone testing a fix needs to confirm the flap is live during the measurement, or they'll conclude it's fixed when it isn't.
2. A uid mismatch is not required. The container user's uid equals the host uid, so ownership matches exactly whenever the flap is inactive. The bogus
0isn't a uid-mapping problem — it's the attribute itself being wrong intermittently.3. Multiple independent bind mounts flap. A directory mount and a separate single-file mount both reported
0:0within the same window.I could not corroborate the bind-mount-root-vs-children distinction: during my sampling the flap was inactive on every path, root and child alike.
Isolating it without rebuilding anything
GIT_CONFIG_NOSYSTEM=1makes git ignore/etc/gitconfig, so you can A/B the workaround against the bug in the same loop iteration on the same path — which matters given how bursty this is:Workaround for anyone landing here.
safe.directoryis additive across config scopes and only honoured from system/global, so a system-scope entry baked into the image survives a host~/.gitconfigbind-mounted over the container's global config:That only stops git consulting ownership — the flap is untouched, so anything else reading uid on a bind mount can still get
0.One question: this reproduces under Apple Virtualization Framework per the report — does it also reproduce under Docker VMM? Several recent bind-mount bugs differ between the two, and that would help localize it.
dvg-p4 commentedon Sep 1, 2026
I can reproduce the issue (on docker desktop 4.88.1 and 4.89.0) just by
touching any file in a directory -- the directory gets misreported as being owned by root:root (0:0) for almost exactly one second after eachtouch:So, the trigger is predictable -- anything that touches a file within the mount -- but can appear sporadic, since there's a whole lot of things that touch files.
dvg-p4 commentedon Sep 1, 2026
The directory that gets its stat messed up seems to be the mount root, when any file within the mount is touched (not the immediate parent as I'd first thought):
reprex
dvg-p4 commentedon Sep 2, 2026
I was only able to reproduce this with VirtioFS, not with gRPC FUSE. So, switching to the latter could be a workaround for now, here's where to find it in the settings:
setting to change
mjswensen commentedon Sep 6, 2026
For what it's worth, I was also seeing it with gRPC FUSE. 😔
bsousaa commentedon Sep 7, 2026
Can you please test with Desktop 4.90?
idontsov commentedon Sep 21, 2026
I get this issue on v4.91.0
bsousaa commentedon Sep 21, 2026
@idontsov can you please generate a diagnostics id?
idontsov commentedon Sep 22, 2026
@bsousaa
Diagnostics id: 3CA66781-E093-4EC3-9BC5-D8191CFB7A22/20260922094345
Let me know if you need the devcontainer configuration, I can share the reproduction repository as well.
karim-elessawy-arm commentedon Oct 3, 2026
Hello,
Just heads up that this was incorrectly marked as fixed in 4.90.0 release notes - last point under 'All platforms' section
Release notes for 4.90.0 (All platforms)
It is still reproducible on 4.93.0 with Apple Virtualization with VirtioFS (haven't tried gRPC FUSE)
It seems also that touching a nested file, causes the nested directory to switch to 0:0 for ~ 1 second
results in
ab commentedon Oct 14, 2026
@bsousaa The bug still exists on Desktop 4.95.0 (242831). Is there anything else that would be helpful to identify a fix?
bsousaa commentedon Oct 19, 2026
This should be fixed with 4.96. Can you please give it a try when it releases?
ab commentedon Oct 19, 2026
Looks to be fixed in 4.96, thank you!
karim-elessawy-arm commentedon Oct 20, 2026
Yes solved for both root mount and nested mount directories - Thank you