Skip to content

tests: three gates blamed roost for the machine being out of ptys - #154

Merged
navbytes merged 1 commit into
mainfrom
test/skip-when-no-pty
Aug 21, 2026
Merged

tests: three gates blamed roost for the machine being out of ptys#154
navbytes merged 1 commit into
mainfrom
test/skip-when-no-pty

Conversation

@navbytes

Copy link
Copy Markdown
Owner

Found by running the suite against a 15-minute soak holding ~500 live
panes. Most of this module's pty-driving tests already skip when
openpty fails — SKIP eof-sweep gate: no pty available, and friends.
Three did not, and each misreported the environment as a product defect:

  • a_resize_mid_gesture_drops_the_freeze.expect("sh must spawn"),
    so a gate about gesture state dies with failed to openpty: Device not configured. Now skips, like its neighbours.

  • spawn_failure_keeps_the_real_cause_reachable_via_alternate_format
    the subtle one. It asserts that spawning a nonexistent program is
    contextualised spawning {program}. With no pty, PtyPane::spawn
    fails earlier, at openpty, so the assertion compared
    left: "openpty" against right: "spawning roost-test-…" — a
    message that says nothing true about roost and points at the wrong
    layer entirely. It now recognises that failure and skips.

  • wait_timeout_exits_nonzero_and_distinct_from_runtime_and_usage_errors
    (tests/cli.rs) — the subtlest. spawn_or_skip only needs a pty for
    roost; with none left, roost starts fine and every pane is born
    exited. wait --until needs_input on a dead pane then resolves
    immediately and correctly (a dead pane will never reach it) with exit
    0, so a gate about the timeout branch failed with
    left: Some(0), right: Some(3) — reporting a resolved wait as a
    broken exit code. A new pane_is_live helper checks the fixture is
    actually running a shell first.

None of these is a roost bug, which is exactly the problem: three
different confusing failure signatures for one environmental cause, on
a class of machine (a loaded laptop, a shared CI runner) where it will
recur. The suite already had the right stance; these three had drifted
out of it.

Verified the way it was found: with the soak still holding its panes,
the full suite goes from 2-3 misleading failures to 0, with the
affected gates skipping and saying why.

Found by running the suite against a 15-minute soak holding ~500 live
panes. Most of this module's pty-driving tests already skip when
`openpty` fails — `SKIP eof-sweep gate: no pty available`, and friends.
Three did not, and each misreported the environment as a product defect:

- `a_resize_mid_gesture_drops_the_freeze` — `.expect("sh must spawn")`,
  so a gate about *gesture state* dies with `failed to openpty: Device
  not configured`. Now skips, like its neighbours.

- `spawn_failure_keeps_the_real_cause_reachable_via_alternate_format` —
  the subtle one. It asserts that spawning a nonexistent program is
  contextualised `spawning {program}`. With no pty, `PtyPane::spawn`
  fails *earlier*, at `openpty`, so the assertion compared
  `left: "openpty"` against `right: "spawning roost-test-…"` — a
  message that says nothing true about roost and points at the wrong
  layer entirely. It now recognises that failure and skips.

- `wait_timeout_exits_nonzero_and_distinct_from_runtime_and_usage_errors`
  (tests/cli.rs) — the subtlest. `spawn_or_skip` only needs a pty for
  *roost*; with none left, roost starts fine and every **pane** is born
  `exited`. `wait --until needs_input` on a dead pane then resolves
  immediately and correctly (a dead pane will never reach it) with exit
  0, so a gate about the *timeout* branch failed with
  `left: Some(0), right: Some(3)` — reporting a resolved wait as a
  broken exit code. A new `pane_is_live` helper checks the fixture is
  actually running a shell first.

None of these is a roost bug, which is exactly the problem: three
different confusing failure signatures for one environmental cause, on
a class of machine (a loaded laptop, a shared CI runner) where it will
recur. The suite already had the right stance; these three had drifted
out of it.

Verified the way it was found: with the soak still holding its panes,
the full suite goes from 2-3 misleading failures to 0, with the
affected gates skipping and saying why.
@navbytes
navbytes merged commit 77a8fc3 into main Aug 21, 2026
3 checks passed
@navbytes
navbytes deleted the test/skip-when-no-pty branch August 21, 2026 07:24
navbytes added a commit that referenced this pull request Aug 21, 2026
…e request (#155)

v0.1.10 shipped at 01:48 UTC today, before any of this session's work
landed, so everything below is unreleased. Touching
`.github/release-request` dispatches Release, which builds four targets,
creates the `v0.1.11` tag from Cargo.toml itself, and publishes with
SHA256SUMS.txt.

**Two silent data-loss bugs.**

- `Alt+w` on the last pane — whose own prompt calls it "quit roost" —
  removed the pane and *then* quit, so `shutdown` saved a tab whose
  layout named a pane its map no longer had. The repair minted a blank
  `shell` spec on next launch, and the session id, cwd, title and note
  of the pane you quit on were gone. `Alt+q` on the same pane kept all
  of them. (#147)
- A recycled pane id inherited the dead pane's session-detection clock.
  `last_shell_seen` and `pending_detect` were the only two
  `PaneId`-keyed maps `close_pane_id` did not prune, so a pane taking
  that id could scan hours back and resume a stranger's conversation,
  permanently. (#141)

**Idle cost down ~13x.** 2.75% CPU and 888 B/s of terminal traffic, to
0.2–0.4% and ~62 B/s, flat in the pane count at every step because it
was all fixed per-frame chrome: a `String` allocated per cell per frame
(#140); the quick-launch picker's `$PATH` probe running ~1,500 `stat(2)`
a second with no dialog on screen, plus the whole help table, every
frame (#144); an unconditional 30 fps repaint (#146); and 12 fps even
with nothing animating (#149).

**Desktop notifications changed channel, and this one is user-visible.**
They used to fork `osascript`, which macOS attributes to **Script
Editor** — there is no flag for that; a CLI cannot post under its own
name. roost now asks the terminal instead, with a bell and an `OSC 9`,
which is the mechanism SPEC-parity P2 already named. Ghostty, iTerm2,
WezTerm and kitty raise a real notification from it, attributed to
themselves, and it survives ssh. **A host that ignores OSC 9 (Apple
Terminal, Alacritty) now gets only the bell.** (#152)

And a pane's notification no longer arrives twice: there were two
emitters, invisible as a duplicate only while one of them was the
`osascript` fork. The named one won, so one banner, saying which pane,
and silence for the pane you are already looking at. (#153)

**Three new fuzzers** over input surfaces nothing reached before — the
mouse router, the key router, and paste — each walking a layout being
reshaped underneath it, drawing a real frame and re-checking the layout
fuzzer's own invariants after every step. The mouse one found #147 on
seed 15. (#147, #148)

**rustfmt and clippy, enforced in CI** (#150), tuned to the style the
tree already had rather than imposed: the config was picked by measuring
churn (556 hunks vs 1,632 for the naive setting). The lint set is in
`Cargo.toml`'s `[lints]` tables so it binds locally too, and three
candidate lints were measured and rejected. It found ten `unsafe` blocks
missing the `SAFETY:` comment every other one in the tree has.

Plus: the reformat kept out of `git blame` (#151), and three test gates
that blamed roost for the machine being out of ptys (#154).

Verified before cutting: 1,048 tests, clippy clean at `-D warnings`,
`fmt --check` clean. Both agent adapters checked end to end against live
binaries (`pi --session …`, `claude --resume …`), the session-resolution
table across all five adapters, the 45s status decays timed against the
clock, and two 15-minute soaks (~78k random keystrokes each, ~1,800
panes) with no orphan, no leak and no unreadable workspace.

Note for after the merge: `HOMEBREW_TAP_TOKEN` is still unset, so
release.yml will skip the tap sync again (it did for v0.1.10). The
formula needs the usual hand-sync.
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