pulumi/pulumi · error

extracting symlink %s: %w

Error message

extracting symlink %s: %w

What it means

Wraps an os.Symlink failure after the destination path was verified clear. The kernel refused to create the symlink at `path` pointing to header.Linkname.

Source

Thrown at sdk/go/common/util/archive/archive.go:131

		target = filepath.Clean(target)
		if target != cleanDir && !strings.HasPrefix(target, cleanDir+string(os.PathSeparator)) {
			return fmt.Errorf("symlink %s points outside the extraction directory", header.Name)
		}

		linkDir := filepath.Dir(path)
		if _, err := os.Stat(linkDir); err != nil {
			if err = os.MkdirAll(linkDir, 0o0700); err != nil {
				return fmt.Errorf("extracting dir %s: %w", linkDir, err)
			}
		}

		if _, err := os.Lstat(path); err == nil {
			if err = os.Remove(path); err != nil {
				return fmt.Errorf("removing existing file %s before creating symlink: %w", path, err)
			}
		}
		if err := os.Symlink(header.Linkname, path); err != nil {
			return fmt.Errorf("extracting symlink %s: %w", path, err)
		}
	default:
		return fmt.Errorf("unexpected plugin file type %s (%v)", header.Name, header.Typeflag)
	}

	return nil
}

// ExtractTGZ uncompresses a .tar.gz/.tgz file into a specific directory.
func ExtractTGZ(r io.Reader, dir string) error {
	// Convert to absolute path so that path traversal checks in extractFile are reliable.
	absDir, err := filepath.Abs(dir)
	if err != nil {
		return fmt.Errorf("resolving destination path: %w", err)
	}

	gzr, err := gzip.NewReader(r)
	if err != nil {

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Extract into a filesystem that supports symlinks (NTFS/ext4, not FAT/exFAT)
  2. On Windows, enable Developer Mode or run with symlink privilege (SeCreateSymbolicLinkPrivilege)
  3. Ensure no concurrent process is extracting the same plugin; clear the plugin dir and retry
  4. Check write permissions on the destination directory

Example fix

// before
# extracting onto /media/usb (FAT32) -> symlink not supported
// after
# set PULUMI_HOME to an ext4/NTFS path
export PULUMI_HOME=/home/user/.pulumi
Defensive patterns

Strategy: validation

Validate before calling

tmp, err := os.MkdirTemp("", "pulumitest")
if err == nil {
	defer os.RemoveAll(tmp)
	if err := os.Symlink("target", filepath.Join(tmp, "probe")); err != nil {
		return fmt.Errorf("filesystem does not support symlinks: %w", err)
	}
}

Try / catch

if err := extractFile(r, header, dir); err != nil {
	if strings.Contains(err.Error(), "extracting symlink") {
		return fmt.Errorf("cannot create symlink at %s (unsupported fs or missing privilege); set PULUMI_HOME to a symlink-capable volume", dir)
	}
	return err
}

Prevention

When it happens

Trigger: Extracting a tar.TypeSymlink where os.Symlink fails: path already exists (EEXIST race), filesystem doesn't support symlinks (e.g. FAT/exFAT mounts, some Windows configs without privileges), or permission denied in the parent directory.

Common situations: Extracting plugin archives on filesystems without symlink support (Windows FAT32 USB, network shares, Docker volumes on some setups); concurrent installs racing on the same path.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/90178e2a879dfa82. Report an issue: GitHub.