New: upterm join, a guest command that joins a session and rejoins on its own when the connection drops. The host's view now offers it first, with the plain ssh line under it for guests without upterm.
New
upterm joinjoins a session from the connect string the host shares:ssh://,ws://orwss://ID@host, a bareID@host, or thessh …line upterm prints, pasted whole. If the connection drops, it resets most terminal modes the session had set, says why it's waiting and when it tries next (Enter retries, Ctrl-C stops), and rejoins with the same connect string;~.leaves. It exits with the session's status. It offers keys as ssh does (the agent's, then the default key files; a key is generated only when there are none) and checks the relay's key against known_hosts. It doesn't read~/.ssh/config: use-i, or plainssh, for per-host settings.- The host's view,
upterm session info,upterm session currentandupterm --helpshow a➤ Join:line above➤ SSH:.session info -o jsonandupterm host --detach -o jsoncarryjoinCommandbesidesshCommand. A ws or wss relay behind a path gets no Join line, sinceupterm jointakes no path. - A forced command (
--force-command) starts again each timeupterm joinrejoins, so don't join one whose effects mustn't repeat withupterm join.
Behaviour changes
upterm hostandupterm joincheck a relay certificate from an authority no@cert-authorityline trusts by the certificate's own key, as OpenSSH does. A stale pin of the public relay now reads as a changed key rather than an unknown host, a plainssh-keyscanline for a self-signed relay now verifies it, and a certificate whose signer is@revokedis refused.- When the hosted command exits or the session is stopped,
upterm hostwaits for each guest to receive its exit status before closing its connection to the relay: one round trip normally, and at most about 2 s more when a guest doesn't answer. A guest that asks for a shell after that is refused rather than accepted and cut off. upterm attachno longer prints(status 0)when a session ends normally; any other status is still shown.- On a self-hosted relay with no Consul and both ws and ssh listeners, a ws guest is now always handed to the relay's own SSH proxy, as an ssh guest is. Joining a session on another node takes one more hop inside the relay, and a refusal arrives when the guest's channel opens rather than in a banner.
Fixes
- Guests' terminals no longer answer the host program's terminal queries. DECRQM (tmux 3.8 typed
2026;2$yinto the pane), DCS queries such as XTGETTCAP, kitty graphics queries and queries wrapped for tmux are now filtered from guests' output. - A guest sometimes got no exit status when the session's command exited: a plain
sshguest exited 255 ("remote command exited without exit status"), andupterm joinwaited for a host that wasn't coming back. - The REMOTE HOST IDENTIFICATION HAS CHANGED warning shows the fingerprint of the key the relay presented, not the pinned one.
- On Windows,
upterm jointreats anSSH_AUTH_SOCKnobody listens on as no agent. A waiting line no longer erases the line above it after the terminal is widened. upterm session infoshowsendingas soon as a session starts to tear down, so a script that reads it the momentupterm hostreturns is no longer toldready, with a connect string, for a session that has ended.upterm session waitis still the way to read how it ended.
Docs
- The Herdr guide's jump to your machine's own sshd is now four ordered steps (sshd, forwarding, the alias, the host key), records the host key before
herdr --remoteneeds it, and has a troubleshooting line forConnection closed by UNKNOWN port 65535.
Compatibility
upterm joinjoins any host's session through any relay. Through a relay older than 0.37.0, a refusal from another relay node can't be told from a missing session, soupterm joinkeeps waiting rather than stopping on it; through one older than 0.34.0, that holds for any key refusal.- The host's view gains a
➤ Join:block above➤ SSH:, and its JSON gainsjoinCommand. action-upterm readssshCommandand is unaffected.
Changelog
- 6937739: docs(herdr): record the sshd's host key before herdr --remote, and tighten the guide (#643) (@owenthereal)
- 9fb46af: feat(cli): advertise upterm join above the ssh line (#641) (@owenthereal)
- 504be5a: feat(cli): shorter upterm join status lines; a pasted ssh command joins (#644) (@owenthereal)
- 90a028e: feat(cli): upterm join (#638) (@owenthereal)
- b5f76ea: feat(join): the core of upterm join, wasm-clean and unused until the command lands (#630) (@owenthereal)
- fad908b: fix(host): check a relay certificate from an unknown authority by its own key (#635) (@owenthereal)
- fd83024: fix(host): give guests their exit status before the tunnel closes (#640) (@owenthereal)
- d9bf776: fix(host): print the presented key's fingerprint in the changed-key warning (#637) (@owenthereal)
- f49b98b: fix(host): say a session is ending once its teardown begins (#633) (@owenthereal)
- 09fe481: fix(io): filter DCS, kitty graphics and tmux-wrapped queries from guests' output too (#636) (@owenthereal)
- 18347dd: fix(io): filter terminal queries by grammar family, so guests stop answering tmux 3.8 (#634) (@owenthereal)
- 665873f: fix(server): send a ws guest to the SSH proxy unless its node came from the store (#642) (@owenthereal)
- 38e96f3: fix: upterm join follow-ups from #638's review (#639) (@owenthereal)
- b6a4ced: test(e2e): wait for a session to end before reading how it ended (#631) (@owenthereal)
- 5f1aa42: test(host): wait for reconnecting before reading the status in the gap (#632) (@owenthereal)