Skip to content

Ship herdr alongside tmux - #6406

Merged
dhh merged 16 commits into
quattrofrom
herdr
Aug 9, 2026
Merged

Ship herdr alongside tmux#6406
dhh merged 16 commits into
quattrofrom
herdr

Conversation

@dhh

@dhh dhh commented Jul 27, 2026

Copy link
Copy Markdown
Member

herdr is a terminal workspace manager built for AI coding agents. This installs it and ships a config that lands as close to config/tmux/tmux.conf as herdr allows, so the muscle memory carries over.

Keybindings

tmux sessions map to herdr workspaces, windows to tabs, panes to panes. herdr accepts arrays of bindings per action, so every tmux key that had both a prefix form and a direct -n form keeps both — close_pane = ["prefix+x", "alt+esc"], switch_tab = ["prefix+1..9", "alt+1..9"], and so on.

Two keys had to move because tmux won the collision: rename_pane to prefix+shift+o (herdr's default prefix+shift+p is our previous-session) and resize mode to prefix+ctrl+arrows (herdr's default prefix+r is our rename-window). Directional resize on C-M-S-arrows uses the native resize_pane_* bindings that our omarchy-herdr build carries (submitted upstream as herdrdev/herdr#2558); each press moves the divider by a 0.05 ratio step, roughly resize-pane -L 5.

Verified empirically rather than guessed: split_vertical is the side-by-side split, so prefix h and prefix v keep the meanings they have in our tmux config.

Tabs

herdr draws the active tab as panel_bg-colored text on an accent-colored fill. Under the terminal theme panel_bg is a dark grey, which is unreadable on the accent. Setting panel_bg = "black" gives black-on-blue — the same pair as our status-left "#[fg=black,bg=blue,bold]". The tab bar background becomes ANSI black rather than fully transparent as a result; panel_bg drives both and there is no separate token.

What doesn't carry over

  • A secondary prefix. herdr takes one, so C-b is gone.
  • M-S-Left/Right swap-window. No herdr action and no tab move CLI.
  • Vi copy-mode keys. herdr's copy mode has its own.
  • history-limit. herdr caps scrollback by bytes instead.

The alert hooks are moot now that bdcdfeb removed that system.

Dev layouts

hdl, hds, hdlm, hsl mirror the tmux ones, keyed off the HERDR_PANE_ID/HERDR_TAB_ID/HERDR_WORKSPACE_ID that herdr exports into every pane. Every split passes --no-focus, so focus stays on the editor pane — which also sidesteps the bug in tdl, where the closing select-pane -t "$opencode_pane" targets a variable that function never sets. hsl tiles into a real grid because herdr has no select-layout tiled.

herdr installs as the herdr package from the Omarchy repo — currently a fork build carrying our patches until they land upstream — rather than through mise, so the client and server can never drift onto different wire protocols. The pane-border patch has already been merged upstream and the resize keybindings are submitted as herdrdev/herdr#2558; once everything is included, the same package name just starts tracking upstream releases. Existing installs pick everything up through the migration, which also retires any mise-managed herdr and seeds the config only when the user doesn't already have one; omarchy refresh herdr stays the explicit way to take the shipped defaults.

— 🤖 Claude, posting on behalf of @dhh

dhh and others added 2 commits July 27, 2026 15:56
Installs herdr through the mise shim, ships the matching config as an
Omarchy default, and adds the usual refresh/restart pair. The keybindings
map tmux sessions to workspaces, windows to tabs, and keep both the prefix
and direct bindings from config/tmux/tmux.conf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hdl, hds, hdlm, and hsl drive herdr through its socket API instead of
tmux. hsl tiles into a real grid since herdr has no select-layout tiled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings July 27, 2026 22:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 27, 2026 23:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 8, 2026 08:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

config/herdr/config.toml:74

  • This detached custom command has no pane-local HERDR_PANE_ID, so --current can act on the server's focused pane rather than the pane that received the shortcut. Use the HERDR_ACTIVE_PANE_ID provided to custom commands and pass it explicitly with --pane.
command = "herdr pane resize --current --direction up --amount 0.03"

config/herdr/config.toml:79

  • This detached custom command has no pane-local HERDR_PANE_ID, so --current can act on the server's focused pane rather than the pane that received the shortcut. Use the HERDR_ACTIVE_PANE_ID provided to custom commands and pass it explicitly with --pane.
command = "herdr pane resize --current --direction right --amount 0.03"

default/bash/fns/herdr:108

  • pane_count is used directly in arithmetic without validation. For 0 or a negative value, the function still includes the current pane and runs the command once; malformed arithmetic input can also abort the layout logic. Reject anything other than a positive integer before calculating the grid.
  local count="$1"

config/herdr/config.toml:64

  • This is a detached custom command, so it does not run with the pane's HERDR_PANE_ID; --current can therefore resolve via the server's current focus instead of the pane that received the key. Herdr supplies HERDR_ACTIVE_PANE_ID to custom commands specifically for this purpose, so pass that explicit pane ID to keep the resize attached to the invoking pane.
command = "herdr pane resize --current --direction left --amount 0.03"

config/herdr/config.toml:69

  • This detached custom command has no pane-local HERDR_PANE_ID, so --current can act on the server's focused pane rather than the pane that received the shortcut. Use the HERDR_ACTIVE_PANE_ID provided to custom commands and pass it explicitly with --pane.

This issue also appears in the following locations of the same file:

  • line 74
  • line 79
command = "herdr pane resize --current --direction down --amount 0.03"

Comment thread default/bash/fns/herdr
Comment on lines +88 to +98
local dirpath="${dir%/}"

if $first; then
# Reuse the current tab for the first project
herdr pane run "$HERDR_PANE_ID" "cd '$dirpath' && hdl $ai $ai2" >/dev/null
first=false
else
local pane_id
pane_id=$(herdr tab create --cwd "$dirpath" --no-focus | jq -r '.result.root_pane.pane_id')
herdr pane run "$pane_id" "hdl $ai $ai2" >/dev/null
fi
dhh and others added 5 commits August 8, 2026 02:34
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
herdr tab create follows the focused workspace without --workspace, so
switching workspaces while hdlm loops scatters the new tabs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Splitting the first column repeatedly inserted each new column between it
and the previous one, so uneven counts put the spare row in a middle
column instead of the last.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 8, 2026 09:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

default/bash/fns/herdr:97

  • This pipeline also masks herdr tab create failures because the pipeline status comes from jq; empty input is accepted and pane_id becomes empty. Capture and validate the creation response so the layout aborts rather than running commands against an invalid pane ID.
      pane_id=$(herdr tab create --workspace "$HERDR_WORKSPACE_ID" --cwd "$dirpath" --no-focus |
        jq -r '.result.root_pane.pane_id')

default/bash/fns/herdr:98

  • As in the first-tab branch, interpolating $ai and $ai2 directly loses argument boundaries. For example, hdlm "codex --flag" becomes hdl codex --flag, treating --flag as the second AI instead of part of the first command. Shell-escape each argument before sending the command.
      herdr pane run "$pane_id" "hdl $ai $ai2" >/dev/null

default/bash/fns/herdr:11

  • This pipeline reports jq's status rather than herdr's. Herdr CLI failures are written to stderr, so jq receives empty input, exits successfully, and the helper returns an empty pane ID as if the split succeeded. Capture the Herdr response first, parse it with jq -e, and make every caller return when the helper fails.

This issue also appears on line 96 of the same file.

  herdr pane split "$1" --direction "$2" --ratio "$3" --cwd "$4" --no-focus |
    jq -r '.result.pane.pane_id'

default/bash/fns/herdr:106

  • Only checking for nonempty arguments allows zero, negative, or nonnumeric pane counts. Those values skip the grid-building loop but still add the current pane and run the command once, contradicting the requested count. Require a positive integer before doing any layout work.
  [[ -z $1 || -z $2 ]] && { echo "Usage: hsl <pane_count> <command>"; return 1; }

Comment thread default/bash/fns/herdr Outdated

if $first; then
# Reuse the current tab for the first project
herdr pane run "$HERDR_PANE_ID" "cd '$dirpath' && hdl $ai $ai2" >/dev/null
Copilot AI review requested due to automatic review settings August 8, 2026 09:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (4)

default/bash/fns/herdr:92

  • A valid subdirectory containing a single quote produces a malformed command (for example, foo'bar becomes cd '/.../foo'bar'). Shell-quote the directory and agent arguments before sending the command to the pane.
      herdr pane run "$HERDR_PANE_ID" "cd '$dirpath' && hdl $ai $ai2" >/dev/null

default/bash/fns/herdr:97

  • This second JSON pipeline has the same failure mode as _herdr_split: a failed tab create can yield an empty or null pane ID, after which pane run targets that invalid ID and the loop continues. Preserve the command status and require the expected JSON field.
      pane_id=$(herdr tab create --workspace "$HERDR_WORKSPACE_ID" --cwd "$dirpath" --no-focus |
        jq -r '.result.root_pane.pane_id')

default/bash/fns/herdr:11

  • This pipeline masks split failures because the file is sourced without pipefail: jq exits successfully on empty input, and an API error object becomes the literal pane ID null. The layout then continues issuing commands against an invalid pane; capture/check the Herdr exit status, use jq -e, and make each caller return when the helper fails.

This issue also appears on line 96 of the same file.

  herdr pane split "$1" --direction "$2" --ratio "$3" --cwd "$4" --no-focus |
    jq -r '.result.pane.pane_id'

default/bash/fns/herdr:106

  • Only checking for an empty count lets hsl 0 cmd, negative values, and non-numeric values enter the arithmetic loops; 0 currently launches the command in the existing pane instead of creating zero panes or reporting an error. Reject anything other than a positive integer before using it in arithmetic.
  [[ -z $1 || -z $2 ]] && { echo "Usage: hsl <pane_count> <command>"; return 1; }

Copilot AI review requested due to automatic review settings August 8, 2026 13:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

default/bash/fns/herdr:92

  • This generated shell command breaks for valid project directories containing a single quote, and unescaped AI command values lose their original argument boundaries. Shell-escape each interpolated value before sending the command to the pane.
      herdr pane run "$HERDR_PANE_ID" "cd '$dirpath' && hdl $ai $ai2" >/dev/null

default/bash/fns/herdr:98

  • The AI command values are re-parsed unquoted when the new pane executes this text. For example, an other_ai value containing options or spaces is split across hdl's first and second arguments instead of being forwarded intact.
      herdr pane run "$pane_id" "hdl $ai $ai2" >/dev/null

default/bash/fns/herdr:106

  • hsl 0 cmd (and negative or nonnumeric counts) passes this guard, but the layout code still adds the current pane and runs the command once. Validate that the count is a positive integer before using it in arithmetic.
  [[ -z $1 || -z $2 ]] && { echo "Usage: hsl <pane_count> <command>"; return 1; }

Copilot AI review requested due to automatic review settings August 9, 2026 10:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

default/bash/fns/herdr:11

  • The split pipeline masks Herdr failures: herdr pane split can return a nonzero status with an error response, while jq -r still succeeds and emits null. Every layout then continues using null as a pane ID, leaving a partially created layout and sending commands to invalid targets. Capture and validate the response with jq -e, and make each caller return when _herdr_split fails.
  herdr pane split "$1" --direction "$2" --ratio "$3" --cwd "$4" --no-focus |
    jq -r '.result.pane.pane_id'

default/bash/fns/herdr:106

  • Validate pane_count as a positive integer before using it in arithmetic. Currently hsl 0 <command> still adds the current pane and executes the command once, and nonnumeric input is interpreted by Bash arithmetic rather than rejected.
  [[ -z $1 || -z $2 ]] && { echo "Usage: hsl <pane_count> <command>"; return 1; }

migrations/1785191576.sh:7

  • This quotes the variable expression inside [[ ]], contrary to the repository's Bash style in AGENTS.md:17. Remove the quotes while retaining quotes for literal comparison operands.
[[ -f "$HOME/.config/herdr/config.toml" ]] || omarchy-refresh-config herdr/config.toml

Copilot AI review requested due to automatic review settings August 9, 2026 11:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

config/herdr/config.toml:46

  • These four names are not Herdr keybinding fields, so they are reported as unknown configuration and the direct resize shortcuts are never installed. Herdr exposes only resize_mode as a built-in binding; directional resize must use [[keys.command]] entries that call pane resize for HERDR_ACTIVE_PANE_ID, as described in the PR.
resize_pane_left = "ctrl+alt+shift+left"
resize_pane_down = "ctrl+alt+shift+down"
resize_pane_up = "ctrl+alt+shift+up"
resize_pane_right = "ctrl+alt+shift+right"

Copilot AI review requested due to automatic review settings August 9, 2026 11:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (2)

migrations/1786273938.sh:10

  • This cannot clean up installs created by an earlier revision of this migration. Migration completion is keyed by filename, and omarchy-migrate skips a file once ~/.local/state/omarchy/migrations/<filename> exists (bin/omarchy-migrate:91-96), so users who already ran 1786273938.sh will never execute the replacement package or mise cleanup. Put the corrective steps in a new timestamped migration so those users see it as pending.
# An earlier revision of this migration installed herdr through mise. Drop that
# install so a stale client can't shadow the packaged /usr/bin/herdr with an
# older wire protocol.
rm -f "$HOME/.local/bin/herdr"

default/bash/fns/herdr:111

  • pane_count is only checked for emptiness, so hsl 0 command still runs the command in the current pane, while a nonnumeric value is interpreted as a Bash arithmetic variable and also collapses to an unintended layout. Reject anything other than a positive integer before calculating the grid.
  [[ -z $1 || -z $2 ]] && { echo "Usage: hsl <pane_count> <command>"; return 1; }

@dhh
dhh merged commit dd61d4a into quattro Aug 9, 2026
4 checks passed
@dhh
dhh deleted the herdr branch August 9, 2026 11:57
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.

2 participants