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.

plugin install owner/repo[/subdir]plugin link /path/to/dir
SourceGit clone into a herdr-managed checkoutYour on-disk directory, as-is
[[build]]Runs at installSkipped
IterationReinstall to pick up changescargo build yourself; herdr launches the new binary immediately
Manifest editsRequires 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 link does 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

SymptomLikely causeFix
Log shows “No such file or directory” on the binaryLinked plugin never builtcargo build --release in the plugin dir
Command runs but env vars missingInvoked outside herdr (no HERDR_*)Trigger via plugin action invoke, not by hand
plugin_requires_newer_herdr in the logmin_herdr_version above running herdrLower the floor; gate features at runtime
ui_busy opening a popupAnother modal (Settings, copy mode, popup) openRetry on next event; see tui-pane-plugins.md
duplicate_plugin_action_id at link/installTwo [[actions]] share an idIds must be unique per plugin regardless of platform
Events seem missed during burstsHandler too slow; herdr dropped deliveriesKeep handlers cheap; see events_lost in common-failures.md
Plugin talks to the wrong server when hand-testingInherited HERDR_SOCKET_PATH from the launching herdrenv -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.