hashicorp/nomad · error

copying cannot traverse symlinks

Error message

copying cannot traverse symlinks

What it means

CopyDir walks a source directory tree and copies it to a new location, but it deliberately refuses to follow anything that is not a regular file (symlinks, devices, sockets, etc.). When the fs.WalkDir callback encounters such an entry, it returns this error to abort the copy, preventing symlink-based path escape or copying of non-file entries from the escapingfs package.

Source

Thrown at helper/escapingfs/copydir.go:34

// 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())
		if err != nil {
			return err
		}

		if _, err := io.Copy(w, r); err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Find the offending symlink or non-regular entry in the source directory (e.g. `find <srcdir> ! -type f -not -type d`) and remove it or replace it with a real file
  2. Copy the dereferenced contents instead: use `cp -rL` semantics or resolve the symlink target and copy it as a regular file
  3. If symlinks are intentional, do not use escapingfs.CopyDir; use a copy helper that follows or copies symlinks explicitly (e.g. filepath.WalkDir with custom symlink handling or symlinks/copy via os.Readlink + os.Symlink)
  4. If the symlink is generated by another component, fix that component to materialize regular files instead of links

Example fix

// before
$ find ./task-dir -type l
./task-dir/lib -> /usr/lib/libfoo.so
// after
$ rm ./task-dir/lib
$ cp /usr/lib/libfoo.so ./task-dir/lib
Defensive patterns

Strategy: validation

Validate before calling

func assertNoSymlinks(src string) error {
    return filepath.WalkDir(src, func(p string, d fs.DirEntry, err error) error {
        if err != nil { return err }
        if !d.IsDir() && !d.Type().IsRegular() {
            return fmt.Errorf("non-regular entry %s (type %s) in %s", p, d.Type(), src)
        }
        return nil
    })
}
// call assertNoSymlinks(src) before CopyDir(src, dst)

Prevention

When it happens

Trigger: Calling escapingfs.CopyDir on a source directory that contains a symlink (or any non-regular entry: device, socket, named pipe) anywhere in its tree.

Common situations: Copying job/task directories that contain symlinks created by the OS or user (e.g. Linux client data dirs with symlinks to shared libraries or chroot links); copy operations on directories managed by tools that link shared assets; environments where a symlink was accidentally left in a data directory.

Related errors


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