Common Failures

An error-catalog for plugin development: what each error means, why it happens, and how to fix or design around it. Debug commands live in how-to/debug-plugins.md; the runtime internals behind most of these are in deep-dives/core-runtime-internals.md.

Error reference

Error / symptomCauseResolution
plugin_requires_newer_herdrThe plugin’s min_herdr_version exceeds the running herdr. The check re-runs on every dispatch, not just install.Lower the floor to the oldest herdr that works. Gate newer features at runtime and declare newer events freely (unknown events are only install-preview warnings). See version-compat-doctrine.md.
events_lostHerdr delivers events in full bursts; a reader (your handler) too slow to drain gets dropped deliveries (0.9.2+).Keep handlers cheap: exit early on irrelevant events, avoid network I/O in hooks, debounce instead of reacting to every event. Subscribe before snapshotting so a missed burst is recoverable from api snapshot.
Install/build aborts after a manifest changeHerdr shows an install preview, then runs [[build]]; a manifest edited between preview and build aborts the install.Redo the install with the final manifest in place. Never treat manifest edits as safe mid-flight changes.
ui_busy (“a popup pane is already open”)Plugin tried to open a popup pane while another modal is up: Settings, copy mode, or an existing popup. Popups are session-modal singletons.Catch it and retry on the next event, or fall back to overlay/split placement when content must always open.
Same-id install silently replaces another pluginInstalled plugins are a map keyed by plugin_id; installing an existing id overwrites the entry. Two same-id plugins cannot coexist on one machine.Pick a unique id before shipping: username-prefix (peteretelej.<name>) and check the marketplace index first (see ship-to-marketplace.md). Symptoms of a collision: your action list changes, an unfamiliar plugin root shows in plugin list.
Plugin action gets no selected_textSelection is client presentation state; the server-side context always carries selected_text: None. Tracked as issue #3380.Design actions that do not need the selection, or have them prompt for input. Link handlers get clicked_url instead, which does reach plugins.
Missing tab.focused events with config keybindsTab focus switching via user-configured keybinds does not emit tab.focused on some paths. Tracked as issue #3801.Do not rely on tab.focused alone for correctness. Subscribe to multiple events (pane-topic-sync pattern) and reconcile; the idempotent-reconcile design tolerates missed triggers.
agent_not_idle on agent readAlternate-screen agents (Claude Code, OpenCode) page history via their mouse-scroll UI, which herdr only drives while the agent is idle. While working/blocked/unknown the read is refused.Use --source visible (passive), wait for idle and retry, or ask the agent to write state to a file. See read-agent-state.md.
Prompt retry duplicates the messageagent prompt --wait can stall (agent_prompt_stalled after 5s without activity) or reject with agent_blocked; blind retries send the prompt twice.Read the agent first (agent get) before retrying; treat agent_blocked as terminal until unblocked; observe the completion_seq/response rather than assuming failure.
Handler runs stale code while linkedplugin link skips [[build]]; herdr launches whatever binary is at $HERDR_PLUGIN_ROOT/target/release/.cargo build --release after edits. Confirm the manifest command path and the binary timestamp.
No such file or directory on the binary pathFresh clone linked without ever building.Build once before linking, or add a dev note in the README.
duplicate_plugin_action_id at loadTwo [[actions]] (or panes/link handlers) share an id, even across different platforms gating.Make ids unique within the plugin; platform variants need distinct ids (file-viewer uses -windows suffixes).
invalid_plugin_min_herdr_versionmin_herdr_version missing or not a bare semver.Set an explicit semver value like "0.7.0".
Pane freezes at a stale topic; plugin never renames itThe pane’s live label stopped matching the plugin’s stored label (single-label state file + concurrent runs), so the plugin classified it as manually renamed.Store a history of written labels, not just the latest; include the desired check in ownership so self-healing works. See teardown-pane-topic-sync.md.
Tabs never get renamed; all tabs look “manually renamed”Comparing tab labels against tab.number (persistent id) instead of the switch-position string (the virgin default), once positions drift.Treat the virgin tab label as its 1-based switch position within the workspace.
Popup content cut off / below the foldDefault popup is about 24 rows; your UI needs more.Set explicit width/height on the [[panes]] entry; herdr clamps to the terminal.
Plugin talks to the wrong server in hand testsPlugin panes and manual runs inherit HERDR_SOCKET_PATH from the launching herdr.Clear it for manual runs against a scratch server: env -u HERDR_SOCKET_PATH herdr --session scratch.

Design rules that prevent most of these

  1. Reconcile, do not react. An idempotent handler that recomputes full state survives missed events, bursts, and overlapping runs.
  2. Treat the manifest as a public API. Changing command bytes breaks installs that cached them (herdr < 0.8.0).
  3. Read defensively. Payloads gain fields; #[serde(default)] everywhere.
  4. Fail silent, log everything. stderr from your handler is the user’s debugging trail via plugin log list.
  5. Never store state in HERDR_PLUGIN_ROOT. It is a managed checkout; reinstalling wipes it. Use HERDR_PLUGIN_STATE_DIR.