opentofu/opentofu · error

failed to read symlink %q: %w

Error message

failed to read symlink %q: %w

What it means

The walk's lstat reported the entry as a symlink, but the subsequent os.Readlink failed. On Unix, readlink fails with ENOENT only if the link itself was removed between lstat and readlink - the tree changed under the walk - or with EACCES if the containing directory is not readable. Windows adds junction/reparse-point edge cases.

Source

Thrown at internal/copy/copy_dir.go:102

			return nil
		}

		errg.Go(func() error {

			// we don't want to try and copy the same file over itself.
			if eq, err := SameFile(path, dstPath); err != nil {
				return fmt.Errorf("failed to check if files are the same: %w", err)
			} else if eq {
				return nil
			}

			// If the current path is a symlink, recreate the symlink relative to
			// the dst directory
			if info.Mode()&os.ModeSymlink == os.ModeSymlink {
				target, err := os.Readlink(path)
				if err != nil {
					return fmt.Errorf("failed to read symlink %q: %w", path, err)
				}

				if err := os.Symlink(target, dstPath); err != nil {
					return fmt.Errorf("failed to create symlink %q: %w", dstPath, err)
				}
				return nil
			}
			return copyFile(dstPath, path, info.Mode())
		})
		return nil
	}
	err = filepath.Walk(src, walkFn)
	waitErr := errg.Wait()
	return errors.Join(waitErr, err)
}

// copyFile copies the contents and mode of the file from src to dst.
func copyFile(dst, src string, mode os.FileMode) error {

View on GitHub (pinned to 3561785c48)

Solutions

  1. Stabilize the source tree first (finish builds/cleanups, or copy from a snapshot)
  2. Retry CopyDir; single-entry races usually clear on a second pass
  3. On Windows, identify the failing reparse point and exclude or materialize it before copying
Defensive patterns

Strategy: retry

Try / catch

if err := copy.CopyDir(dst, src); err != nil {
	if strings.Contains(err.Error(), "failed to read symlink") {
		// entry vanished between walk and copy: rerun on a quiesced tree
		err = copy.CopyDir(dst, src)
	}
}

Prevention

When it happens

Trigger: A symlink deleted concurrently with the copy (package managers, build cleanups); a symlink inside a directory whose read permission was revoked; Windows reparse points that readlink cannot handle.

Common situations: Copying node_modules/build trees while a tool prunes them; parallel processes managing the same source directory; cross-platform CI hitting Windows junctions.

Related errors


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/c5b2089e6f3b6623. Report an issue: GitHub.