hasura/graphql-engine · error

failed to remove old symlink: %w

Error message

failed to remove old symlink: %w

What it means

createOrUpdateLink first removes any existing symlink at dst (filepath.Join(binDir, PluginNameToBin(plugin, IsWindows()))) via removeLink before installing the new one. If removeLink fails — because dst exists as a real file/dir, permissions block unlink, or binDir is not writable — this error wraps the OS failure.

Source

Thrown at cli/plugins/util.go:184

	return nil
}

// IsWindows sees runtime.GOOS to find out if current execution mode is win32.
func IsWindows() bool {
	goos := runtime.GOOS

	return goos == "windows"
}

func createOrUpdateLink(binDir, binary, plugin string) error {
	var op errors.Op = "plugins.createOrUpdateLink"

	dst := filepath.Join(binDir, PluginNameToBin(plugin, IsWindows()))

	err := removeLink(dst)
	if err != nil {
		return errors.E(op, fmt.Errorf("failed to remove old symlink: %w", err))
	}

	if _, err := os.Stat(binary); stderrors.Is(err, fs.ErrNotExist) {
		return errors.E(
			op,
			fmt.Errorf(
				"can't create symbolic link, source binary (%q) cannot be found in extracted archive: %w",
				binary,
				err,
			),
		)
	}

	// Create new
	err = os.Symlink(binary, dst)
	if err != nil {
		if IsWindows() {
			// If cloning the symlink fails on Windows because the user

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Inspect the wrapped OS error (EACCES vs EISDIR vs ENOTDIR) to identify the cause.
  2. Manually remove the stale entry: rm <binDir>/<plugin-bin> (or rmdir if it's a directory).
  3. Ensure binDir is writable by the current user (chown/chmod), or configure a user-local bin dir.

Example fix

# before
$ ls ~/.cli/bin/myplugin   # regular file from old manual install
# install fails: failed to remove old symlink

# after
$ rm ~/.cli/bin/myplugin
$ cli plugin install myplugin
Defensive patterns

Strategy: try-catch

Validate before calling

dst := filepath.Join(binDir, pluginBinaryName)
if fi, err := os.Lstat(dst); err == nil && fi.Mode()&os.ModeSymlink == 0 {
	// a real file/dir occupies the link path — remove or relocate it first
	if err := os.RemoveAll(dst); err != nil {
		return err
	}
}

Try / catch

if err := plugins.Install(...); err != nil {
	if errors.Is(err, fs.ErrPermission) && strings.Contains(err.Error(), "remove old symlink") {
		// instruct user to fix bin dir permissions or remove stale file
	}
}

Prevention

When it happens

Trigger: installPlugin → createOrUpdateLink where binDir/<plugin>(.exe) exists as a non-symlink file, or the user lacks write permission on binDir, so os.Remove/os.Lstat in removeLink returns an error.

Common situations: A previous manual copy of the binary into the bin dir; a directory sitting where the link should go; bin dir owned by root after sudo installs; Windows/WSL path quirks.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/3a15065e03af3d53. Report an issue: GitHub.