Skip to content

feat(session): surface exec exit-status / exit-signal via SshSession.exitInfo#232

Merged
kruton merged 1 commit into
connectbot:mainfrom
GlassOnTin:feat/exec-exit-status
Jul 23, 2026
Merged

feat(session): surface exec exit-status / exit-signal via SshSession.exitInfo#232
kruton merged 1 commit into
connectbot:mainfrom
GlassOnTin:feat/exec-exit-status

Conversation

@GlassOnTin

Copy link
Copy Markdown
Contributor

What

Adds SshSession.exitInfo: Deferred<SessionExit?> — the RFC 4254 §6.10 exit-status / exit-signal reports, which the packet loop previously parsed (the kaitai types were already wired into SshMsgChannelRequest) but dropped after logging. Without this, an exec consumer can read stdout/stderr to EOF but never learn how the command terminated.

  • SessionExit sealed interface: Status(code) / Signal(signalName, coreDumped, errorMessage)
  • Completed from the connection's channel-request dispatch for session channels; first report wins per the RFC
  • Resolves to null when the channel closes without a report (servers aren't required to send one), so waiters are never left suspended
  • Metalava signature updated; metalavaCheckCompatibility passes

Why

Haven is adopting sshlib as its second SSH engine (context in GlassHaven/Haven#58); the next slice is remote command execution, where automations branch on exit codes. This was the one missing piece for exec parity — happy to adjust the API shape (e.g. a suspend accessor instead of the exposed Deferred) if you'd prefer.

Tests

  • 4 FakeSshServer flow tests: exit-status completes exitInfo; exit-signal completes it; close-without-report resolves null; first report wins over a later one
  • 2 Testcontainers integration tests against OpenSSH 9.9p2: sh -c 'exit 42'Status(42); a KILL-terminated command → Signal("KILL", …)
  • Full :sshlib:test suite: 1170 tests, 0 failures (unit + integration, Docker available)

GlassOnTin added a commit to GlassHaven/Haven that referenced this pull request Jul 22, 2026
…p probe (#58)

Phase 3 hit an upstream wall worth documenting: sshlib runs exec but
drops the RFC 4254 §6.10 exit-status/exit-signal reports, so
ExecResult.exitStatus cannot be produced honestly — and run_command
automations branch on exit codes. Upstreamed as connectbot/cbssh#232
(SshSession.exitInfo: Deferred<SessionExit?>, flow + OpenSSH-9.9p2
integration tests, 1170 upstream tests green).

Haven side, landable today: ExecContractTest — the engine-agnostic
exec contract (stdout, stderr, exit codes, 1 MiB output, timeout
shape) against MINA with an in-process scripted CommandFactory, run by
JschExecContractTest as the behaviour pin; plus a spike GAP probe
asserting the released sshlib has no exit API — it fails when a
release carries #232, which is the signal to add SshlibExecContractTest
and the engine-aware exec routing.
@GlassOnTin

Copy link
Copy Markdown
Contributor Author

Some context on where this fits, @kruton, for whenever you next have review bandwidth (CI is green across JDK 17/21/25 + CodeQL).

This closes the last gap for adopting sshlib as a second SSH engine in Haven — the migration you kindly kicked off back in GlassHaven/Haven#58 when you reached out about ssh-proto. Since then the exec (#95), subsystem (#98), SFTP (#112) and FIDO2/SK (#146) work all landed upstream, and Haven now has the per-engine building blocks built and tested against real servers:

  • SFTP — shipped and device-verified on Haven's daily driver.
  • Shell / PTY — done, contract-tested against JSch byte-for-byte (banner-first-bytes, echo, resize, clean-exit).
  • Port forwarding (local / remote / dynamic SOCKS5) — done, contract-tested.

The one thing still missing is a truthful command exit status. Haven runs execCommand to detect tmux/zellij/screen sessions and to bootstrap Mosh/Eternal-Terminal, and those flows branch on the exit code — so the library dropping the RFC 4254 §6.10 exit-status/exit-signal report (the kaitai types already parse it; the packet loop just discards it after a debug log) means the exit code can't be surfaced honestly. Rather than fabricate a value, Haven has that whole path gated behind this PR: once it lands in a release, a sshlib-toggled Haven profile can run its terminal, tunnels and file transfer entirely over sshlib instead of JSch.

Happy to reshape the API if you'd prefer something other than an exposed Deferred<SessionExit?> — e.g. a suspend fun awaitExit(): SessionExit? accessor, or folding it into the existing close/lifecycle signalling. Whatever fits the library best; the wire parsing and tests are the substance.

Comment thread sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt Outdated
Comment thread sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt Outdated
Comment thread sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt Outdated
@kruton
kruton force-pushed the feat/exec-exit-status branch from 478277a to 0c4163e Compare July 23, 2026 15:01
…exitInfo

The kaitai schemas already parse the RFC 4254 §6.10 exit-status and
exit-signal channel requests, but the connection's packet loop dropped
them after logging, so exec consumers had no way to learn a command's
exit code.

Adds a sealed SessionExit (Status / Signal) and a Deferred exitInfo on
SshSession, completed from the packet loop when the server reports
termination and resolved to null when the channel closes without a
report (servers are not required to send one). First report wins per
the RFC.

Tests: four FakeSshServer flow tests (status, signal, close-without-
report resolves null, first-report-wins) plus two Testcontainers
integration tests against OpenSSH 9.9p2 (sh -c 'exit 42' and a
KILL-terminated command). Metalava signature updated;
metalavaCheckCompatibility passes.
@kruton
kruton force-pushed the feat/exec-exit-status branch from 0c4163e to d961d41 Compare July 23, 2026 15:02
@kruton
kruton enabled auto-merge (rebase) July 23, 2026 15:04
@kruton
kruton merged commit 32aecf1 into connectbot:main Jul 23, 2026
11 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.

2 participants