Skip to content

fix: wait for a pane's shell to be ready before sending its command - #11

Open
19Naveen wants to merge 1 commit into
yuk1ty:mainfrom
19Naveen:fix/wait-for-shell-ready-before-pane-run
Open

fix: wait for a pane's shell to be ready before sending its command#11
19Naveen wants to merge 1 commit into
yuk1ty:mainfrom
19Naveen:fix/wait-for-shell-ready-before-pane-run

Conversation

@19Naveen

@19Naveen 19Naveen commented Aug 1, 2026

Copy link
Copy Markdown

Problem

Commands configured with command: silently never run. The layout is built
correctly — tabs, splits and ratios are all right — but the panes just sit at
an idle prompt.

--dry-run shows nothing wrong, because the planned argv is correct:

workspace create --label 'demo' --cwd /home/me/project --focus
tab create --index 1 --label 'editor' --cwd /home/me/project --no-focus
pane run TabRoot(1) nvim
pane split TabRoot(1) -> Split(1) --direction down --ratio 0.7 --no-focus
pane run Split(1) npm run dev
...

Running the same file for real leaves both panes at a bare prompt — nvim
never opens, npm run dev never starts.

Cause

CliBackend::run calls herdr pane run immediately after the pane is
created. A shell that has just spawned has not started its line editor yet,
so the command text is echoed to the PTY and then discarded during shell
startup. Reading the pane's scrollback afterwards shows the command sitting
above the shell's own startup banner, with an empty prompt underneath —
the text arrived before there was a line editor to receive it.

The same pane run against the same pane succeeds once the pane is about a
second old, which is what makes this a startup race rather than a quoting or
argv bug.

Reliably reproducible with a heavier interactive shell — Oh My Zsh +
powerlevel10k + a startup banner tool (e.g. fastfetch) is enough to lose the
race every time. A minimal .zshrc/.bashrc starts fast enough to usually
win it, which is likely why this hasn't surfaced before.

Fix

Wait for the pane to settle before sending: poll herdr pane read until its
output stops changing.

Output stability alone is not sufficient on its own — some prompt frameworks
(powerlevel10k's instant prompt is one) paint an early prompt before the
real line editor exists, so a pane can look settled while still not being
ready to receive input. A minimum floor is enforced on top of the stability
check to cover that case.

Both bounds are overridable, for slower machines or for minimal shells that
need no wait at all:

Variable Default Meaning
HERDR_SPREADER_READY_FLOOR_MS 1500 Minimum wait before sending
HERDR_SPREADER_READY_TIMEOUT_MS 10000 Give up waiting and send anyway; 0 disables

CliBackend::with_ready_settings(floor, timeout) exposes the same knobs
in-process.

Tests

  • parse_ms unit tests cover absent, empty, whitespace, invalid, negative and
    valid values, including 0.
  • The fake-herdr integration tests call .with_ready_settings(Duration::ZERO, Duration::ZERO), so they still assert the exact argv sequence and are
    unaffected by polling. They also stay fast — without the opt-out they would
    spend the full timeout per pane run.

cargo fmt, cargo clippy --all-targets -- -D warnings and cargo test
(118 tests) all pass.

Verified

Against herdr 0.7.5 on Linux, applying a multi-tab layout with several panes
each running a different command:. Before this fix, every pane's command
was silently swallowed. After, every pane's command started correctly and
panes with no command were left untouched.

Note

Happy to adjust the shape of this — e.g. a per-pane wait_for_shell: YAML
key, or a --ready-floor-ms flag instead of environment variables — if you'd
prefer the knobs surfaced somewhere else.

A pane that was just created has a shell which has not started its line
editor yet. `herdr pane run` sent immediately after `workspace create`,
`tab create` or `pane split` is echoed to the PTY and then discarded
during shell startup, so the configured `command` silently never runs, or
arrives truncated (`claude --continue` landing as `p`).

This is reliably reproducible with a heavyweight interactive shell (Oh My
Zsh + powerlevel10k + a fastfetch banner) and is invisible in `--dry-run`,
because the planned argv is correct — only the timing is wrong. The same
`pane run` succeeds once the pane is about a second old.

Poll the pane with `pane read` and wait for its output to stop changing
before sending. Output stability alone is not sufficient: powerlevel10k's
instant prompt paints very early, so the pane looks settled while the real
line editor still does not exist. A minimum floor is enforced on top of
the settle check, and both bounds are overridable:

  HERDR_SPREADER_READY_FLOOR_MS    (default 1500)
  HERDR_SPREADER_READY_TIMEOUT_MS  (default 10000, 0 disables)

`CliBackend::with_ready_settings` exposes the same knobs in-process. The
fake-herdr integration tests use it to disable waiting, so they keep
asserting the exact argv sequence and stay fast.
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