docker/compose · error

stat %q: %w

Error message

stat %q: %w

What it means

While preparing a tar-based sync, Tar.Sync stats each PathMapping's HostPath to decide whether to copy it or delete its container counterpart. A missing file is expected (treated as a delete), but any other stat failure (permission denied, path too long, I/O error) aborts the whole sync with 'stat %q: %w'. This is a host-filesystem-level error, not a Docker error.

Source

Thrown at internal/sync/tar.go:81

		client:      client,
	}
}

func (t *Tar) Sync(ctx context.Context, service string, paths []*PathMapping) error {
	containers, err := t.client.ContainersForService(ctx, t.projectName, service)
	if err != nil {
		return err
	}

	var pathsToCopy []PathMapping
	var pathsToDelete []string
	for _, p := range paths {
		if _, err := os.Stat(p.HostPath); err == nil {
			pathsToCopy = append(pathsToCopy, *p)
		} else if errors.Is(err, fs.ErrNotExist) {
			pathsToDelete = append(pathsToDelete, p.ContainerPath)
		} else {
			return fmt.Errorf("stat %q: %w", p.HostPath, err)
		}
	}

	var deleteCmd []string
	if len(pathsToDelete) != 0 {
		deleteCmd = append([]string{"rm", "-rf"}, pathsToDelete...)
	}

	var (
		eg    errgroup.Group
		errMu sync.Mutex
		errs  = make([]error, 0, len(containers)*2) // max 2 errs per container
	)

	eg.SetLimit(16) // arbitrary limit, adjust to taste :D
	for i := range containers {
		containerID := containers[i].ID
		tarReader := tarArchive(pathsToCopy)

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Check permissions on the failing host path (the error's %q names it exactly); chmod/chown the parent directories so the compose process can traverse them
  2. If the file is intentionally gone, delete the stale entry from your sync path list so it hits the ErrNotExist delete branch cleanly
  3. Unmount or repair broken network/FUSE filesystems that return EIO on stat
  4. Re-run compose watch/sync after fixing; if it persists, stat the path manually with the same user to reproduce outside compose

Example fix

# shell: reproduce and fix the exact path named in the error
sudo -u $(whoami) stat /path/from/error   # -> permission denied
sudo chmod o+x /parent/dir               # grant traversal
# then re-run: docker compose watch
Defensive patterns

Strategy: validation

Validate before calling

// before Tar.Sync: verify every host path is stat-able apart from clean NotExist
for _, p := range paths {
    if _, err := os.Stat(p.HostPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
        return fmt.Errorf("pre-check failed for %s: %w", p.HostPath, err)
    }
}

Try / catch

// inspect errors from Tar.Sync with errors.Is/As against fs.ErrPermission to distinguish permission vs I/O causes
if err := syncer.Sync(ctx, svc, paths); err != nil {
    var pathErr *fs.PathError
    if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrPermission) { /* fix perms */ }
}

Prevention

When it happens

Trigger: Calling Tar.Sync(ctx, service, paths) where a p.HostPath exists but the process lacks search permission on a parent directory, the path exceeds NAME_MAX, or the underlying filesystem returns an I/O error. Only errors other than fs.ErrNotExist reach this branch.

Common situations: Build contexts or volume-sync roots with restrictive ownership (e.g. files created by a root container then synced by a non-root compose run), paths behind broken FUSE/NFS mounts, or a path that is a dangling symlink whose parent dir is unreadable.

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/3e70ac3a75e1d91e. Report an issue: GitHub.