hashicorp/nomad · error

could not stat directory: %v

Error message

could not stat directory: %v

What it means

CopyDir in helper/escapingfs walks the source directory tree via fs.WalkDir; when it encounters a directory entry it calls d.Info() to read the directory's metadata so the mode can be preserved. This error wraps a failure of that Info() call, meaning the directory's stat information could not be retrieved during the copy. The copy aborts at that point.

Source

Thrown at helper/escapingfs/copydir.go:29

	"path/filepath"
)

// CopyDir copies a directory's contents to a new location, returning an error
// on symlinks. This implementation is roughly the same as the stdlib os.CopyDir
// but with th e important difference that we preserve file modes.
func CopyDir(src, dst string) error {
	srcFs := os.DirFS(src)

	return fs.WalkDir(srcFs, ".", func(oldPath string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		newPath := filepath.Join(dst, oldPath)
		if d.IsDir() {
			info, err := d.Info()
			if err != nil {
				return fmt.Errorf("could not stat directory: %v", err)
			}
			return os.MkdirAll(newPath, info.Mode())
		}
		if !d.Type().IsRegular() {
			return fmt.Errorf("copying cannot traverse symlinks")
		}

		r, err := srcFs.Open(oldPath)
		if err != nil {
			return fmt.Errorf("could not open existing file: %v", err)
		}
		defer r.Close()
		info, err := r.Stat()
		if err != nil {
			return fmt.Errorf("could not stat file: %v", err)
		}

		w, err := os.OpenFile(newPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode())

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Re-run the copy once the source tree is stable; ensure no concurrent process deletes/renames directories under src.
  2. Check and fix read permissions on the affected source directory (the wrapped %v names it via the walk path).
  3. Copy from local disk instead of a network/ephemeral mount if stat calls are unreliable there.
  4. If racing is unavoidable, snapshot the source (e.g. rsync/cp to a staging dir) and CopyDir from the snapshot.

Example fix

// before
err := escapingfs.CopyDir("/live/data", dst) // dir deleted mid-walk
// after
snapshot, _ := os.MkdirTemp("", "snapshot")
exec.Command("cp", "-a", "/live/data/.", snapshot).Run()
err := escapingfs.CopyDir(snapshot, dst)
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight: ensure source tree is readable before CopyDir
if err := filepath.WalkDir(src, func(p string, d fs.DirEntry, err error) error {
	if err != nil { return err }
	if d.IsDir() { _, err := d.Info(); return err }
	return nil
}); err != nil {
	return fmt.Errorf("source tree not stable/readable: %w", err)
}

Try / catch

err := escapingfs.CopyDir(src, dst)
if err != nil {
	if strings.Contains(err.Error(), "could not stat directory") {
		// transient race or permission issue: retry once after a short wait
		time.Sleep(100 * time.Millisecond)
		err = escapingfs.CopyDir(src, dst)
	}
	return err
}

Prevention

When it happens

Trigger: Calling CopyDir(src, dst) when fs.WalkDir yields a directory whose DirEntry.Info() fails — e.g. the directory was deleted or renamed between the walk listing and the Info() call (TOCTOU race), or permission to stat it was revoked.

Common situations: Copying a live directory tree that another process is concurrently pruning; copying from a mount that is being unmounted or a flaky network filesystem; permission changes (chmod/ACL) applied while walking the tree.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/87477277081c6e4f. Report an issue: GitHub.