Herdr 0.8.0 (checked against d78e3d3). Filing from the plugin-author side.
The gap
Reinstall is v1's documented refresh path — "There is no separate plugin update in v1; reinstall from GitHub to refresh a managed plugin" — and it does the hard parts well: temp checkout, build, atomic swap, config preserved. But it stops there.
A plugin that supervises a long-running process can't finish the job from inside that flow:
[[build]] is too early. run_plugin_build_commands runs in the temp checkout, before the rename into the managed path — so a build step can't restart a service onto code that isn't in place yet.
[[startup]] doesn't fire. Per the docs it runs after a session restore and on live handoff, explicitly not "when a client attaches, config reloads, or a plugin is linked or enabled" — and not after an install either.
[[events]] has no plugin lifecycle. plugin_hook_event_names() is workspace/tab/pane only; there is no plugin.* event to hang this on.
So after a reinstall the plugin is shipping new code while the old process is still running, and the user has to know to invoke a second action to land it. Ours is a web bridge under systemd --user, but this applies to anything holding a socket, a daemon, or a watcher.
The knock-on effect
Without a supported "refresh me" verb, a plugin that wants one-command updates has to reach into the managed checkout with raw git. That means depending on the shape git_checkout() leaves behind — git init + git fetch --depth 1 origin HEAD + git checkout --detach FETCH_HEAD, i.e. detached, shallow, no remote-tracking refs. That shape isn't documented, and we only learned it by reading src/cli/plugin.rs after our update action had been failing with "You are not currently on a branch" for every GitHub-installed user since our first release (AltanS/collie#63). Plugins are guessing at an internal layout because there's no seam.
There's a second, sharper edge in the same area: a plugin that tries to self-heal its registry entry with herdr plugin link <managed path> silently re-registers itself as source.kind = local, after which ensure_replacement_allowed refuses herdr plugin install — removing the user's only remaining way to refresh. We now detect and skip that case, but it's an easy trap to fall into, since plugin link is the documented way to make Herdr notice a changed manifest on older versions.
Either of these would close it
herdr plugin update <id> — refetch the recorded source (honouring requested_ref), rebuild, swap, update resolved_commit. Plugins never touch the managed checkout, and the registry stays truthful about what's on disk.
- A manifest-declared post-install action — e.g.
[plugin] after_install = "restart", invoked after the swap and registration, so a service-backed plugin lands the new code by itself.
(2) is much smaller and composes with what already exists — actions are declared and invokable, this just adds one call site at the end of install. (1) is the more complete answer, and would also let plugins stop shipping their own git logic.
Happy to send a PR for either if you have a preference on shape.
Herdr 0.8.0 (checked against
d78e3d3). Filing from the plugin-author side.The gap
Reinstall is v1's documented refresh path — "There is no separate
plugin updatein v1; reinstall from GitHub to refresh a managed plugin" — and it does the hard parts well: temp checkout, build, atomic swap, config preserved. But it stops there.A plugin that supervises a long-running process can't finish the job from inside that flow:
[[build]]is too early.run_plugin_build_commandsruns in the temp checkout, before the rename into the managed path — so a build step can't restart a service onto code that isn't in place yet.[[startup]]doesn't fire. Per the docs it runs after a session restore and on live handoff, explicitly not "when a client attaches, config reloads, or a plugin is linked or enabled" — and not after an install either.[[events]]has no plugin lifecycle.plugin_hook_event_names()is workspace/tab/pane only; there is noplugin.*event to hang this on.So after a reinstall the plugin is shipping new code while the old process is still running, and the user has to know to invoke a second action to land it. Ours is a web bridge under
systemd --user, but this applies to anything holding a socket, a daemon, or a watcher.The knock-on effect
Without a supported "refresh me" verb, a plugin that wants one-command updates has to reach into the managed checkout with raw git. That means depending on the shape
git_checkout()leaves behind —git init+git fetch --depth 1 origin HEAD+git checkout --detach FETCH_HEAD, i.e. detached, shallow, no remote-tracking refs. That shape isn't documented, and we only learned it by readingsrc/cli/plugin.rsafter our update action had been failing with "You are not currently on a branch" for every GitHub-installed user since our first release (AltanS/collie#63). Plugins are guessing at an internal layout because there's no seam.There's a second, sharper edge in the same area: a plugin that tries to self-heal its registry entry with
herdr plugin link <managed path>silently re-registers itself assource.kind = local, after whichensure_replacement_allowedrefusesherdr plugin install— removing the user's only remaining way to refresh. We now detect and skip that case, but it's an easy trap to fall into, sinceplugin linkis the documented way to make Herdr notice a changed manifest on older versions.Either of these would close it
herdr plugin update <id>— refetch the recorded source (honouringrequested_ref), rebuild, swap, updateresolved_commit. Plugins never touch the managed checkout, and the registry stays truthful about what's on disk.[plugin] after_install = "restart", invoked after the swap and registration, so a service-backed plugin lands the new code by itself.(2) is much smaller and composes with what already exists — actions are declared and invokable, this just adds one call site at the end of install. (1) is the more complete answer, and would also let plugins stop shipping their own git logic.
Happy to send a PR for either if you have a preference on shape.