v0.39.0 rebuilds a session's replay for joining terminals and adds keys for checking session status, richer JSON output, and safer upgrades.
Joining from a different terminal size
- A joining guest or
upterm attachnow receives a rebuilt screen and recent history instead of replaying cursor movements recorded at earlier terminal sizes. This improves joining a desktop session from a phone or a narrower terminal, including Claude Code, shells, editors and terminal monitors. The live stream continues from the same point. #653, #664 - When terminal state cannot be rebuilt faithfully, or rebuilding exceeds its budget, the join uses the previous raw replay and logs why. Unsupported terminal features and unsafe byte sequences trigger this fallback. Valid UTF-8 characters split by the buffer boundary still allow rebuilding. #676
- The joining terminal's previous screen moves into its scrollback. Session state such as titles, the working directory and the last clipboard write per selection is restored; a scrollback-clear in the captured output also clears the joiner's older scrollback.
- Limits: the visible screen keeps the session's layout until the application repaints after a resize. History preserves wrapped rows as separate lines, so copying can insert extra newlines; tabs become spaces, and width shrinks can truncate text. A raw fallback retains the old replay's limitations.
Session commands and terminal output
- In
upterm hostandupterm attach, type~?at the start of a line to see the session's guests, relay connection and available keys without detaching. Type~~?to send a literal~?. Through an outer SSH connection with its default escape, use~~?to reach upterm's status key, or connect withssh -e none.upterm join's keys are unchanged. #662 session info,wait,stop,setandupterm attachaccept a session ID as well as a name.session listprints a plain table instead of a full-screen view and gains-o jsonand--hide-client-ip. Session names and output formats complete in the shell. #656, #663- JSON output adds relay, access, permission and count-availability fields;
session currentreports more of the session's state, andupterm version -o jsonis new. Existing fields remain, and all JSON timestamps use UTC. - Session views say who can join and what guests may do.
Host:becomesRelay:andConnected Clients:becomesConnected:.upterm attachgains--hide-client-ip; explicitly setting it to false shows addresses in CI too. - Errors print once in the terminal as
upterm: …, without a usage block. Join waits print the cause once and redraw only the countdown. Plain SSH guests receive clearer refusal messages from updated relays, and every command's help documents its keys and exit statuses.
Upgrade and configuration fixes
upterm upgradeselects the correct archive on Linux and reports offline or failed downloads instead of panicking. It replaces the binary being run, respects Homebrew, Scoop, deb and rpm ownership, and names the package-manager command to use. Without a version argument it no longer installs a release older than the running build. If an older Linux binary hits the archive-selection bug while upgrading, install v0.39.0 through your package manager or from the release assets below. #651, #655upterm config editaccepts editor arguments such asEDITOR="code --wait", checks keys and values after editing, and exits 1 when the file is invalid. Help, version and completion remain usable with a bad config. #654--server ssh://HOSTdefaults to port 22. SSH addresses with no hostname are refused. Charset tracking now follows cursor saves and restores, preventing ordinary text from appearing as line-drawing characters on a later join. #666, #661
Exit-status changes for scripts
- A stopped session, or one ended by a signal to its daemon, returns 1 to the foreground host, attached terminals and guests, even if the command handles SIGHUP and exits 0. A command that finishes on its own keeps its status.
session waitstill returns 0 for a stop. #658 - Usage and configuration errors return 255 from
attachandjoin, and 125 fromsession wait. Unknown commands and extra arguments that were previously ignored now fail; an unknown command with--helpalso exits 1. - At the host's acceptance prompt, Ctrl-C/SIGINT exits 130 and SIGTERM exits 143. Neither starts the session. With
host --detach, success still means the session started; usesession waitfor its eventual result.
Deprecation: move per-run settings to command-line flags before v0.40
These environment variables and config keys still apply in v0.39.0, with a warning. Starting in v0.40, the affected commands will ignore them; pass the corresponding flag instead. #652
| Environment variable / config key | Affected commands | Use instead |
|---|---|---|
UPTERM_OUTPUT / output
| host, session info, session current
| -o / --output
|
UPTERM_NAME / name
| host
| --name
|
UPTERM_DETACH / detach
| host
| --detach
|
UPTERM_WAIT / wait
| join
| --wait
|
UPTERM_JOIN_TIMEOUT / join-timeout
| session set only
| --join-timeout
|
host --join-timeout keeps reading env/config. The new output flags on session list and version are command-line-only from the start.
Compatibility
Upgrade the host to get the rebuilt replay; guests can continue using plain SSH or an older upterm. New JSON fields are additive, and permission fields are omitted when an older daemon cannot report them. For Go callers, set Host.ClaimTimeout per instance; the package-level host.ClaimTimeout is deprecated.
Changelog
- 3c164cb: build(deps): bump github.com/charmbracelet/x/ansi from 0.11.7 to 0.11.9 (#673) (@dependabot[bot])
- ab0132b: build(deps): bump github.com/cli/go-gh/v2 from 2.16.1 to 2.16.2 (#649) (@dependabot[bot])
- cf3f9fc: build(deps): bump github.com/getsentry/sentry-go/slog from 0.49.0 to 0.50.0 (#647) (@dependabot[bot])
- 02ac233: build(deps): bump github.com/jpillora/chisel from 1.12.1 to 1.13.0 (#672) (@dependabot[bot])
- 86f7b1f: build(deps): bump github.com/prometheus/client_golang from 1.24.1 to 1.25.0 (#674) (@dependabot[bot])
- 9df5ace: build(deps): bump github/codeql-action from 4.38.2 to 4.38.3 (#650) (@dependabot[bot])
- b3a3780: build(deps): bump github/codeql-action from 4.38.3 to 4.39.0 (#675) (@dependabot[bot])
- 4c5a6f0: build(deps): bump golang.org/x/crypto from 0.57.0 to 0.58.0 (#648) (@dependabot[bot])
- e66b9e7: build(deps): bump golang.org/x/term from 0.46.0 to 0.47.0 (#646) (@dependabot[bot])
- 368edbb: feat(cli): complete session names and output formats (#656) (@owenthereal)
- a17eda3: feat(cli): every command's help says what it's for, its keys and its exit statuses (#666) (@owenthereal)
- 6268ce7: feat(cli): session commands take a session ID, list prints a table, and -o json says more (#663) (@owenthereal)
- 8113429: feat(cli): ~? shows the session's status, and the host's view says who can join (#662) (@owenthereal)
- 845c706: feat(replay): fall back on any sequence the render doesn't know (#676) (@owenthereal)
- 78a9c31: feat(server): plain ssh guests read why they were refused, and refusals say what to do (#665) (@owenthereal)
- a423873: feat: give a joiner the session's screen, rebuilt for its terminal (#664) (@owenthereal)
- 580bf14: feat: size the pty for a joiner before its replay, and log every resize in the ring (#653) (@owenthereal)
- a7947cb: fix(cli): one upterm: line per error, and each command's exit code (#652) (@owenthereal)
- 878dda6: fix(config): config edit works with code --wait and says what's wrong (#654) (@owenthereal)
- 7cb0689: fix(host): a stopped session exits 1, and every end line says why (#658) (@owenthereal)
- e1e2095: fix(io): give a joiner the charset a cursor restore leaves (#661) (@owenthereal)
- 0ab48e7: fix(join): say the cause once and redraw only when the next try is (#657) (@owenthereal)
- 1b53213: fix(upgrade): pick the upterm archive and report network failures (#651) (@owenthereal)
- 35728e7: fix(upgrade): replace the running binary, and leave managed installs to their manager (#655) (@owenthereal)
- 4bff2f0: refactor: attach, server and host take their timing per instance, not from package variables (#668) (@owenthereal)
- a859823: test(host): join-timeout tests fire the deadline themselves, on a join clock the host takes per instance (#670) (@owenthereal)
- 32ae3d9: test(server): drive four store and forwarding ordering bugs through their checkpoints, in every order (#669) (@owenthereal)
- 5643a6f: test: check the errors tests receive from channels (#660) (@owenthereal)
- 99b5fb2: test: host and server waits wake on the change they wait for, not on polls (#671) (@owenthereal)
- 0ce1464: test: run liveness, the lease keeper, AsyncWriter and the first join in fake time (#667) (@owenthereal)