Skip to content

feat: add herdr as an alternative multiplexer backend - #304

Draft
rubenfiszel wants to merge 2 commits into
mainfrom
feat/herdr-backend
Draft

feat: add herdr as an alternative multiplexer backend#304
rubenfiszel wants to merge 2 commits into
mainfrom
feat/herdr-backend

Conversation

@rubenfiszel

@rubenfiszel rubenfiszel commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Adds herdr as an alternative to tmux for backing a project's panes, selected per project with a new multiplexer: config key. Default stays tmux; omitting the key changes nothing.

# .webmux.yaml
multiplexer: herdr

Verified end to end

Against live herdr 0.8.0 / protocol 19, in an isolated named session:

  • webmux add feature-x → one workspace, one tab, 2 panes matching the profile — pane.process_info confirms claude running in the agent pane and zsh in the shell pane
  • webmux listopen · webmux close → tab removed · webmux listclosed
  • webmux open re-creates the tab; a second worktree adds a second tab to the same workspace

What changed

SessionGateway (backend/src/adapters/session-gateway.ts) — the old TmuxGateway, renamed and made multiplexer-agnostic. Two cleanups came with it:

  • Async, since herdr round-trips a unix socket. Nearly every call site was already in an async method.
  • setWindowOption is gone from the interface. session-service no longer pokes pane-base-index / automatic-rename / allow-rename; BunTmuxGateway.createWindow applies them itself. A new focusWindow replaces the CLI's hand-rolled tmux select-window.

HerdrGateway (backend/src/adapters/herdr.ts) — maps session→workspace, window→tab, pane→pane, joined by label rather than id, because webmux persists names and herdr ids don't survive a restart.

herdr 0.8.0 quirks this works around

The socket API accepts more parameters than it honours. Each of these was found by running it, and three of them only surfaced once there was more than one tab in play:

  1. One request per connection. Reusing a socket silently hangs every later call. Only events.subscribe is long-lived.
  2. herdr never self-daemonizes. ping reports detached_server_daemon: false; everything else returns server_not_running. Unlike tmux start-server, ensureServer() spawns and detaches herdr server itself, then polls.
  3. pane.list ignores tab_id / tab / workspace_id and always returns every pane in the session. The adapter filters by each pane's own tab_id — without this, pane index 0 of a worktree resolves into a different tab.
  4. pane.split ignores pane_id and splits whatever pane is focused. The adapter focuses the anchor first and restores prior focus afterwards, preserving tmux's detached split-window -d behaviour for parked panes.
  5. pane.swap needs source_pane_id/target_pane_id — a shape the CLI doesn't expose, so the adapter uses the socket directly. This is what lets the existing parked-pane tab system work unchanged.

pane.send_text and pane.read do honour pane_id. EXPECTED_PROTOCOL is pinned and a mismatch logs a warning, since herdr is pre-1.0.

Correcting an earlier version of this description: it claimed explicit pane.split targeting was confirmed. It wasn't — the original spike had a single tab, so the target was the focused pane. Points 3 and 4 above are the corrected findings.

Not supported in herdr mode: the web terminal

A real limitation, not an oversight. The browser terminal works by attaching a tmux grouped session (new-session -t + window-size latest) so each tab gets an independently-sized view of a shared window. herdr has no equivalent: no method accepts rows/cols (panes live on a server-side virtual screen), and pane.read is a snapshot rather than an incremental stream. Attaching now returns an explicit error pointing you at your own terminal. Everything else — worktree lifecycle, dashboard, git state, PRs, Linear, conversations — is unaffected.

Notes for review

  • bin/ is not covered by any tsconfig and has never been typechecked. I checked it with an ad-hoc config while working: it has 10 pre-existing errors, unchanged by this PR (verified by stashing). Worth a follow-up, out of scope here.
  • The parked-pane/swap tab machinery is untouched and works on herdr as-is.

Tests

766 pass, 0 fail (738 baseline + 28 new, incl. 24 HerdrGateway tests against a stub socket that reproduces the quirks above). Backend typecheck clean.

Docs: docs/herdr.md.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
webmux 22aeeb0 Commit Preview URL

Branch Preview URL
Aug 06 2026, 09:38 PM

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant