Debug Plugins
Plugin failures are quiet by design: your stderr never interrupts the user. Everything you need is in herdr plugin subcommands plus a couple of environment habits.
Read the plugin log first
Every invocation (event, action, pane, startup, build) is recorded with its exit code, stdout, and stderr:
herdr plugin log list --plugin peteretelej.tab-topic
herdr plugin log list --plugin peteretelej.tab-topic --limit 20
Each entry shows the command, exit code, and captured output. If your handler printed nothing and exited 0, it ran and chose to do nothing (normal for filtered events). Non-zero exit or error text lands here too. Under the hood herdr emits a PluginCommandFinished internal event with log_id, exit_code, stdout, stderr; the log list is the human window onto it.
Make diagnosis easy: print a one-line reason for every skip and failure.
eprintln!("skip: status=working (only blocked/done notify)");
Inspect what is registered
herdr plugin list # installed + linked plugins, ids, roots
herdr plugin action list --plugin peteretelej.tab-topic
plugin list shows each plugin’s plugin_root, which answers the most common “why is it running old code?” question: for linked plugins the root is your working tree; for installed plugins it is the managed checkout.
Install vs link: the build difference
plugin install owner/repo[/subdir] | plugin link /path/to/dir | |
|---|---|---|
| Source | Git clone into a herdr-managed checkout | Your on-disk directory, as-is |
[[build]] | Runs at install | Skipped |
| Iteration | Reinstall to pick up changes | cargo build yourself; herdr launches the new binary immediately |
| Manifest edits | Requires a fresh install (change aborts mid-install otherwise) | Live on next invocation |
The dev loop is therefore: herdr plugin link plugins/tab-topic, then cargo build --release after edits, then trigger the event. No reinstall, no reload. If an action seems to run stale code while linked, check that the manifest command points at $HERDR_PLUGIN_ROOT/target/release/<bin> and that you actually rebuilt.
Gotcha:
plugin linkdoes not run[[build]], so a fresh clone linked without building fails with “no such file” on the binary path. Build once before linking.
Test restore and handoff with disposable sessions
Startup hooks ([[startup]]) fire once after session restore and again after a live handoff. To verify your plugin survives restarts without touching real work:
# 1. start a named scratch session
herdr --session plugintest
# 2. inside it, trigger your plugin, then quit herdr
# 3. start again on the same session and confirm state restored cleanly
herdr --session plugintest
The session name keeps it separate from your default session. When you want a genuine from-scratch restore test, clear your plugin’s state file in $HERDR_PLUGIN_STATE_DIR first; reinstalling preserves config and state, so it is not a reset button.
Common invocation failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Log shows “No such file or directory” on the binary | Linked plugin never built | cargo build --release in the plugin dir |
| Command runs but env vars missing | Invoked outside herdr (no HERDR_*) | Trigger via plugin action invoke, not by hand |
plugin_requires_newer_herdr in the log | min_herdr_version above running herdr | Lower the floor; gate features at runtime |
ui_busy opening a popup | Another modal (Settings, copy mode, popup) open | Retry on next event; see tui-pane-plugins.md |
duplicate_plugin_action_id at link/install | Two [[actions]] share an id | Ids must be unique per plugin regardless of platform |
| Events seem missed during bursts | Handler too slow; herdr dropped deliveries | Keep handlers cheap; see events_lost in common-failures.md |
| Plugin talks to the wrong server when hand-testing | Inherited HERDR_SOCKET_PATH from the launching herdr | env -u HERDR_SOCKET_PATH herdr --session scratch for manual runs |
Invoke actions directly while iterating:
herdr plugin action invoke peteretelej.tab-topic.sync
That runs the exact command the manifest declares, with the exact environment herdr provides, without waiting for a real event.