herdrdev/herdr · warning · io::Error

failed to {operation} managed plugin checkout at {}; close a

Error message

failed to {operation} managed plugin checkout at {}; close any Herdr plugin panes or plugin commands using that checkout, then retry: {err}

What it means

plugin_checkout_lifecycle_error (src/cli/plugin.rs:1591) wraps filesystem errors from creating/removing managed plugin checkout directories. On Windows, when the raw error is PermissionDenied, it returns a richer message telling you that a Herdr plugin pane or plugin command still holds the checkout open. Other platforms surface the raw fs error unchanged.

Source

Thrown at src/cli/plugin.rs:1591

        return Ok(());
    };
    let path = PathBuf::from(path);
    if !path.exists() {
        return Ok(());
    }
    if !is_expected_managed_path(plugin, &path) {
        return Err(std::io::Error::other(format!(
            "refusing to delete unmanaged plugin path: {}",
            path.display()
        )));
    }
    std::fs::remove_dir_all(&path)
        .map_err(|err| plugin_checkout_lifecycle_error("remove", &path, err))
}

fn plugin_checkout_lifecycle_error(operation: &str, path: &Path, err: io::Error) -> io::Error {
    if cfg!(windows) && err.kind() == io::ErrorKind::PermissionDenied {
        return io::Error::new(
            err.kind(),
            format!(
                "failed to {operation} managed plugin checkout at {}; close any Herdr plugin panes or plugin commands using that checkout, then retry: {err}",
                path.display()
            ),
        );
    }
    err
}

fn is_expected_managed_path(plugin: &InstalledPluginInfo, path: &Path) -> bool {
    let Ok(path) = path.canonicalize() else {
        return false;
    };
    let expected = crate::plugin_paths::managed_checkout_path(&plugin.plugin_id);
    let Ok(expected) = expected.canonicalize() else {
        return false;
    };

View on GitHub (pinned to f457cff4f2)

Solutions

  1. Close all Herdr plugin panes and any terminals/editors using that checkout, then retry the operation
  2. Stop builds or dev servers running inside the plugin checkout
  3. Retry after a short delay if antivirus/indexing was transiently locking files
  4. As a last resort, restart Herdr so all child processes release handles, then retry
Defensive patterns

Strategy: retry

Try / catch

for attempt in 0..3 {
    match remove_checkout(&path) {
        Ok(()) => break,
        Err(e) if cfg!(windows) && e.kind() == io::ErrorKind::PermissionDenied => {
            std::thread::sleep(std::time::Duration::from_secs(1 << attempt));
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Calling the checkout remove/create path (e.g. std::fs::remove_dir_all at src/cli/plugin.rs:1588-1590, or the corresponding create) on Windows while a process still has files in the checkout open — a running plugin pane, a plugin command shell, an editor, or antivirus scanning the tree.

Common situations: Uninstalling/updating a plugin while its pane is open in another Herdr tab; a build process (cargo/node) still running in the checkout; Windows Defender or a file indexer locking files; the directory being open in Explorer.

Related errors


AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28). Data as JSON: /api/errors/f5b15112b1073a20. Report an issue: GitHub.