hasura/graphql-engine · error

could not uninstall symlink of plugin: %w

Error message

could not uninstall symlink of plugin: %w

What it means

Uninstall() removes the symlink/shim the installer placed in the bin directory (BinPath()/PluginNameToBin(name)) via removeLink. If that removal fails, this error is returned and the plugin directory and receipt are left untouched, so the uninstall is incomplete but restartable.

Source

Thrown at cli/plugins/plugins.go:253

// Uninstall will uninstall a plugin.
func (c *Config) Uninstall(name string) error {
	var op errors.Op = "plugins.Config.Uninstall"

	if _, err := c.LoadManifest(c.Paths.PluginInstallReceiptPath(name)); err != nil {
		if stderrors.Is(err, fs.ErrNotExist) {
			return errors.E(op, ErrIsNotInstalled)
		}

		return errors.E(
			op,
			fmt.Errorf("failed to look up install receipt for plugin %q: %w", name, err),
		)
	}

	symlinkPath := filepath.Join(c.Paths.BinPath(), PluginNameToBin(name, IsWindows()))
	if err := removeLink(symlinkPath); err != nil {
		return errors.E(op, fmt.Errorf("could not uninstall symlink of plugin: %w", err))
	}

	pluginInstallPath := c.Paths.PluginInstallPath(name)
	if err := os.RemoveAll(pluginInstallPath); err != nil {
		return errors.E(
			op,
			fmt.Errorf("could not remove plugin directory %q: %w", pluginInstallPath, err),
		)
	}

	pluginReceiptPath := c.Paths.PluginInstallReceiptPath(name)

	err := os.Remove(pluginReceiptPath)
	if err != nil {
		return errors.E(
			op,
			fmt.Errorf("could not remove plugin receipt %q: %w", pluginReceiptPath, err),
		)

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Check what exists at BinPath()/PluginNameToBin(name) (ls -la) — if it is a real file or directory rather than a symlink, remove it manually
  2. Fix ownership/permissions of the bin directory (chown -R $(whoami) or run once with appropriate privileges) and retry uninstall
  3. On Windows, ensure no process is running the plugin binary and that the file is not locked
  4. Re-run Uninstall afterwards — the operation is idempotent once the path is clear

Example fix

# before
$ ls -la ~/.cli/bin/myplugin   # regular file left by manual copy
$ rm ~/.cli/bin/myplugin
# after: uninstall succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

binTarget := filepath.Join(cfg.Paths.BinPath(), PluginNameToBin(name, IsWindows()))
if fi, err := os.Lstat(binTarget); err == nil && fi.Mode()&os.ModeSymlink == 0 {
	// a real file/dir sits where the symlink should be; remove it first
	_ = os.RemoveAll(binTarget)
}

Try / catch

if err := cfg.Uninstall(name); err != nil && strings.Contains(err.Error(), "could not uninstall symlink") {
	// inspect and remove the path at BinPath manually, then retry Uninstall
}

Prevention

When it happens

Trigger: Calling Uninstall when the symlink in the bin directory cannot be removed: permission denied (bin dir owned by root or another user), the path exists but is a real file on Windows where a shim was expected, or the path is a non-empty directory removeLink refuses to delete.

Common situations: CLI was installed via sudo so the bin dir is root-owned; a user replaced the symlink with a real binary; Windows-specific shim/file mismatch; the bin directory is on a read-only mount.

Related errors


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