← Blogs

Moving from tmux to herdr

herdrtmuxterminalkeyboard

I moved from tmux to herdr today. Herdr[1] is a terminal workspace manager built around coding agents — panes, tabs and workspaces like tmux, plus a sidebar that knows which agent is working and which one is waiting on you.

The migration took an afternoon. First thing I did was port Nightingale, this site’s dark theme, onto herdr’s kanagawa base — same ink, same accent, every token overridden in config.toml. It felt like home immediately.

Except one thing. My pane switches kept vanishing.

The vanishing switch

In tmux, prefix h then prefix l is one motion. Hop left, hop right, done before you finish the thought.

In herdr, the first switch worked. The second one, typed at normal speed, did nothing. No pane switch. No stray character in the pane. Prefix mode just gone, so I had to press Ctrl+B again and wait a beat before the key would land.

Wait. A multiplexer where waiting is part of the keybinding.

Blaming the wrong thing

I blamed the obvious ghost first: tmux’s bind -r. In tmux, the pane-select keys are bound repeatable — after one use they stay live for repeat-time, 500 ms by default, so a fast second key repeats the action instead of falling through to the pane.[2] Herdr has no repeat mechanism, and there’s an open request asking for one.[3]

The theory fit perfectly. Missing repeat window, dropped second key, case closed.

Then a five-second test broke it: press Ctrl+B, keep holding Ctrl, press h. Nothing — every single time, at any speed. Release Ctrl before the h, and it works every single time, at any speed.

It was my Ctrl finger

Typing Ctrl+B h fast means Ctrl is still down when the h lands. Herdr doesn’t see h — it sees Ctrl+h. Nothing is bound to Ctrl+h in prefix mode, so it silently swallows the key and exits prefix mode. No switch, no character, no feedback.

The “delay” I was trying to fix never existed. The pause that made it work was just my finger leaving the Ctrl key.

Every chorded interface has to decide what a leaked modifier means. tmux doubles up several of its own defaults for this reason — C-b C-o and C-b o do the same thing. Herdr decides it means nothing, silently.

The fix

Herdr’s keybindings take arrays, and prefix+ctrl+h is valid syntax. Four lines in ~/.config/herdr/config.toml:

[keys]
focus_pane_left  = ["prefix+h", "prefix+ctrl+h"]
focus_pane_down  = ["prefix+j", "prefix+ctrl+j"]
focus_pane_up    = ["prefix+k", "prefix+ctrl+k"]
focus_pane_right = ["prefix+l", "prefix+ctrl+l"]

Reload with prefix+shift+r. Now the modifier can leak all it wants — Ctrl+B h switches panes with Ctrl held down the whole way.

I wrote the whole thing up for the herdr maintainers in a discussion post, workaround included. Their contribution model is unusual — no outside pull requests at all, maintainers implement accepted reports with their own agents — so a precise report is the whole contribution.

What else is missing

The Ctrl leak was my hand. These are herdr’s.

No bind -r. prefix h l costs two prefix presses where tmux charges one. Discussion 599 asks for a repeat window; nothing has landed.

No conditional keybindings. tmux can ask a question before it acts: if -F "#{@pane-is-vim}" sends Ctrl+h to vim when vim owns the pane, to the multiplexer when it doesn’t.[4] Herdr has neither the conditional nor a per-pane option to keep the answer in, so plugins rebuild the check on every keypress. vim-herdr-navigation (80 stars) polls process info through bash and jq.[5] herdr-nvim-nav (15 stars) ships a C binary and a marker file in $XDG_CACHE_HOME.[6] herdr-splits.nvim (44 stars) has the most features.[7] All three exist because herdr is missing one primitive.

I installed none of them. I never ran vim-tmux-navigator under tmux either, and the four lines above cover it.

No theme files. Pick the closest built-in, then override every token — that’s the comment at the top of my config. Nightingale is kanagawa plus twenty-odd [theme.custom] lines. It works, and there’s nothing to hand anyone else.

No swap-window. Moving a space up or down the sidebar took a shell script against the socket API, bound to prefix+shift+up/down. The socket path only resolves for the default session. When it doesn’t resolve, nothing happens and nothing tells you why.

Resurrect is experimental. pane_history = true keeps scrollback across server restarts. Built in, where tmux wants two plugins, and flagged experimental.

The plugin index is a GitHub topic. Search herdr-plugin and you find most of what I’d want: floax, automatic-rename with jump keys, a sessionizer, a tmuxinator-style YAML layout tool.[8] No review, no sandbox. Every one of them is a stranger’s code holding my terminal.

One day in, none of that has sent me back. The fix that mattered today was four lines of config and a correct diagnosis.