Skip to content

hqsh 0.2.0: shared attach, like tmux (--steal, --read-only) - #2

Merged
ralyodio merged 1 commit into
mainfrom
feat/shared-attach
Oct 6, 2026
Merged

ralyodio merged 1 commit into
mainfrom
feat/shared-attach

Conversation

@ralyodio

@ralyodio ralyodio commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Several clients can attach to one session at once, tmux style.

Behaviour

  • Shared by default: every attached client gets all output; any client's input goes to the PTY.
  • --steal / -d (tmux attach -d): the others get a new DETACHED frame (type 11) and exit with "detached by another client" instead of reconnecting. Sent on the first connection only; reconnects never steal.
  • --read-only / -r: the daemon ignores this client's INPUT (and the client does not send keys). Ctrl-^ . still detaches.
  • Size: smallest cols and smallest rows among attached clients (read-only included), recomputed on attach/detach/RESIZE.
  • Resume: per client, from the shared ring, as before.
  • ACK: the ring is trimmed only to the minimum ACK across attached clients; the 4 MiB budget still caps it.
  • Backpressure: each client has its own writer and cursor into the ring. The PTY is paced to the most caught-up client (2 MiB window): one client = plain backpressure as in 0.1 (no drop-and-reconnect loop on a slow link); with several, a lagging or stalled client never holds the others, and is dropped once it falls off the ring or blocks a write for 5 s, then resumes via the gap path.
  • server list --json: keeps attached (bool), adds clients (count). Text: attached (N clients) when N > 1.
  • Silent: nothing is printed when others attach.

Wire compatibility

  • The HELLO flags byte sits after a second 0 byte; with no flags the HELLO is byte-identical to 0.1, so the shared default costs nothing.
  • WELCOME flag bit1 = daemon does shared attach. STATUS appends a u16 client count (0.1 daemons: 1/0 inferred).
  • 0.1 client on a 0.2 daemon: a shared read-write client (after a steal it reconnects, since it does not know DETACHED).
  • 0.2 client on a 0.1 daemon: works (verified), with 0.1 take-over semantics; end the session and re-attach to get shared attach.

Tests

  • Unit: minSize, minAck, ACK-min trim against the ring, mustWait pacing, ring Behind/First, HELLO flags and STATUS encodings.
  • Integration (real /bin/sh over pipes): two clients see each other's input; stty size follows the min rule through resize and detach; read-only input is ignored; --steal sends DETACHED and drops the others; a stalled client is dropped while the other receives all 6 MB contiguous; a real client.Run pair plus a stealer (the stolen ones exit 0 with one dial each).
  • E2E on dev2 over ssh localhost: 2 clients + a read-only one + a stealer; a 0.2 client on a running 0.1 daemon; a 0.1 client alongside a 0.2 client on a 0.2 daemon.

Also

  • Release workflow: a concurrency group per tag. v0.1.0 got two push events for one tag 2 s apart (the workflow has a single trigger), so two runs raced to create the release.
  • CI: go test -race on Linux.

🤖 Generated with Claude Code

Several clients can now attach to one session at once. Every client gets
all output and any client can type; the PTY is the smallest cols and rows
among them. --steal (-d) detaches the others (they get the new DETACHED
frame and exit instead of reconnecting); --read-only (-r) watches only.

- HELLO carries an optional flags byte after a second 0 byte; with no
  flags the HELLO is byte for byte the 0.1 one. WELCOME bit1 marks a
  shared-attach daemon. STATUS adds a client count; server list --json
  keeps "attached" and adds "clients".
- Each client has its own writer and its own cursor into the shared ring,
  so a slow client never stalls the PTY or the others. The PTY is paced to
  the most caught-up client (2 MiB window), so a single slow client still
  gets plain backpressure as in 0.1; a client that falls off the ring or
  cannot take a frame for 5 s is dropped and resumes via the gap path.
- ACK trims the ring only to the lowest ACK across attached clients.
- Release workflow: one run per tag (concurrency), after v0.1.0 got two
  racing runs. CI: go test -race on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 0e53d90 into main Oct 6, 2026
5 checks passed
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