hasura/graphql-engine · warning
file %q is not a symlink (mode=%s)
Error message
file %q is not a symlink (mode=%s)
What it means
removeLink refuses to delete a path that is not a symbolic link (on non-Windows platforms) as a safety measure, so uninstalling a plugin never removes a regular file or directory the user placed in the bin directory. It reports the offending file's mode so you can see what it actually is.
Source
Thrown at cli/plugins/util.go:240
}
}
return nil
}
// removeLink removes a symlink reference if exists.
func removeLink(path string) error {
var op errors.Op = "plugins.removeLink"
fi, err := os.Lstat(path)
if stderrors.Is(err, fs.ErrNotExist) {
return nil
} else if err != nil {
return errors.E(op, fmt.Errorf("failed to read the symlink in %q: %w", path, err))
}
if fi.Mode()&os.ModeSymlink == 0 && !IsWindows() {
return errors.E(op, fmt.Errorf("file %q is not a symlink (mode=%s)", path, fi.Mode()))
}
if err := os.Remove(path); err != nil {
return errors.E(op, fmt.Errorf("failed to remove the symlink in %q: %w", path, err))
}
return nil
}
// PluginNameToBin creates the name of the symlink file for the plugin name.
// It converts dashes to underscores.
func PluginNameToBin(name string, isWindows bool) string {
name = strings.ReplaceAll(name, "-", "_")
name = "hasura-" + name
if isWindows {
name += ".exe"
}View on GitHub (pinned to 724551b9ae)
Solutions
- Inspect the file: ls -l ~/.hasura/plugins/bin/<name> to confirm it is a regular file, not a link.
- If it is a manually installed copy, delete it yourself (rm, not the CLI) and re-run the CLI command.
- If it is a directory, remove it with rm -rf and reinstall the plugin so the CLI manages the symlink.
- Never force the CLI past this; the guard protects your data.
Example fix
# before $ hasura plugin uninstall myplugin file "/home/u/.hasura/plugins/bin/myplugin" is not a symlink (mode=-rwxr-xr-x) # after $ rm /home/u/.hasura/plugins/bin/myplugin $ hasura plugin uninstall myplugin # succeeds
Defensive patterns
Strategy: validation
Validate before calling
func isSymlinkOrMissing(path string) bool {
fi, err := os.Lstat(path)
if errors.Is(err, fs.ErrNotExist) { return true }
return err == nil && fi.Mode()&os.ModeSymlink != 0
}
// call before Uninstall/createOrUpdateLink
if !isSymlinkOrMissing(dst) { /* move/delete the regular file yourself */ } Type guard
func isSymlink(path string) bool {
fi, err := os.Lstat(path)
return err == nil && fi.Mode()&os.ModeSymlink != 0
} Try / catch
// treat as a warning: the CLI is protecting a real file
if err := uninstall(); err != nil && strings.Contains(err.Error(), "is not a symlink") {
// manually inspect/remove the file, then retry
} Prevention
- Only let the CLI manage files in the plugins bin directory.
- Audit the bin dir periodically: find ~/.hasura/plugins/bin ! -type l.
When it happens
Trigger: Calling Uninstall or createOrUpdateLink when the destination path exists as a regular file, directory, hardlink, or other non-symlink inode on Linux/macOS. On Windows this check is skipped (IsWindows guard).
Common situations: A user manually copied the plugin binary into the bin directory instead of installing via the CLI, or a previous installer version wrote a real file rather than a symlink; later uninstall then trips this guard.
Related errors
- failed to stat the currently executed path (%q): %w
- failed to resolve the symlink of the currently executed vers
- failed to create a symlink from %q to %q: %w
- failed to read the symlink in %q: %w
- failed to remove the symlink in %q: %w
AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28).
Data as JSON: /api/errors/6e5bd89d770403f8.
Report an issue: GitHub.