Conversation
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>
There was a problem hiding this comment.
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--currentcan act on the server's focused pane rather than the pane that received the shortcut. Use theHERDR_ACTIVE_PANE_IDprovided 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--currentcan act on the server's focused pane rather than the pane that received the shortcut. Use theHERDR_ACTIVE_PANE_IDprovided 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_countis used directly in arithmetic without validation. For0or 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;--currentcan therefore resolve via the server's current focus instead of the pane that received the key. Herdr suppliesHERDR_ACTIVE_PANE_IDto 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--currentcan act on the server's focused pane rather than the pane that received the shortcut. Use theHERDR_ACTIVE_PANE_IDprovided 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"
| 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 |
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>
There was a problem hiding this comment.
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 createfailures because the pipeline status comes fromjq; empty input is accepted andpane_idbecomes 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
$aiand$ai2directly loses argument boundaries. For example,hdlm "codex --flag"becomeshdl codex --flag, treating--flagas 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 thanherdr's. Herdr CLI failures are written to stderr, sojqreceives empty input, exits successfully, and the helper returns an empty pane ID as if the split succeeded. Capture the Herdr response first, parse it withjq -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; }
|
|
||
| if $first; then | ||
| # Reuse the current tab for the first project | ||
| herdr pane run "$HERDR_PANE_ID" "cd '$dirpath' && hdl $ai $ai2" >/dev/null |
There was a problem hiding this comment.
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'barbecomescd '/.../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 failedtab createcan yield an empty ornullpane ID, after whichpane runtargets 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:jqexits successfully on empty input, and an API error object becomes the literal pane IDnull. The layout then continues issuing commands against an invalid pane; capture/check the Herdr exit status, usejq -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;0currently 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; }
There was a problem hiding this comment.
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_aivalue containing options or spaces is split acrosshdl'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; }
There was a problem hiding this comment.
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 splitcan return a nonzero status with an error response, whilejq -rstill succeeds and emitsnull. Every layout then continues usingnullas a pane ID, leaving a partially created layout and sending commands to invalid targets. Capture and validate the response withjq -e, and make each caller return when_herdr_splitfails.
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_countas a positive integer before using it in arithmetic. Currentlyhsl 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 inAGENTS.md:17. Remove the quotes while retaining quotes for literal comparison operands.
[[ -f "$HOME/.config/herdr/config.toml" ]] || omarchy-refresh-config herdr/config.toml
There was a problem hiding this comment.
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_modeas a built-in binding; directional resize must use[[keys.command]]entries that callpane resizeforHERDR_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"
There was a problem hiding this comment.
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-migrateskips a file once~/.local/state/omarchy/migrations/<filename>exists (bin/omarchy-migrate:91-96), so users who already ran1786273938.shwill 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_countis only checked for emptiness, sohsl 0 commandstill 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; }
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.confas 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
-nform 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_panetoprefix+shift+o(herdr's defaultprefix+shift+pis our previous-session) and resize mode toprefix+ctrl+arrows(herdr's defaultprefix+ris our rename-window). Directional resize onC-M-S-arrowsuses the nativeresize_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, roughlyresize-pane -L 5.Verified empirically rather than guessed:
split_verticalis the side-by-side split, soprefix handprefix vkeep the meanings they have in our tmux config.Tabs
herdr draws the active tab as
panel_bg-colored text on anaccent-colored fill. Under theterminalthemepanel_bgis a dark grey, which is unreadable on the accent. Settingpanel_bg = "black"gives black-on-blue — the same pair as ourstatus-left "#[fg=black,bg=blue,bold]". The tab bar background becomes ANSI black rather than fully transparent as a result;panel_bgdrives both and there is no separate token.What doesn't carry over
C-bis gone.M-S-Left/Rightswap-window. No herdr action and notab moveCLI.history-limit. herdr caps scrollback by bytes instead.The alert hooks are moot now that bdcdfeb removed that system.
Dev layouts
hdl,hds,hdlm,hslmirror the tmux ones, keyed off theHERDR_PANE_ID/HERDR_TAB_ID/HERDR_WORKSPACE_IDthat herdr exports into every pane. Every split passes--no-focus, so focus stays on the editor pane — which also sidesteps the bug intdl, where the closingselect-pane -t "$opencode_pane"targets a variable that function never sets.hsltiles into a real grid because herdr has noselect-layout tiled.herdr installs as the
herdrpackage 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 herdrstays the explicit way to take the shipped defaults.— 🤖 Claude, posting on behalf of @dhh