docs/superpowers/plans/2026-09-05-ssh-error-classification.md
Ref: Size: 6.6 KiB History
# SSH failure classification — follow-up sprint
The user authorized this bounded follow-up after accepting native tiling Sprint 5.
The native tiling milestone remains complete. This sprint closes the shared
handoff debt recorded in [RETRO.md](../../../RETRO.md), using the
[native sprint workflow](../../native-sprint-workflow.md).
## Scope and acceptance
Recognize SSH authentication refusal separately from an unavailable host when
the endpoint announcement fails. A native pane must stop automatic reconnect on
a recognized authentication refusal, retain its identity and diagnostic, and
offer explicit Retry after credentials are corrected. Apply this both during
initial workspace restoration and after a previously attached session disconnects.
An unavailable host or an unclassified announcement EOF remains eligible for
bounded-backoff reconnect. Unknown diagnostics must not be guessed to mean
authentication refusal. Preserve cancellation and bounded shutdown, join-only
restoration, and the prohibition on automatically retrying End or creation.
Terminal layout policy, tabs, pane rearrangement, and credential UI are outside
this sprint.
Acceptance uses actual SSH authentication and real daemon sessions, with an
independent attempt counter, session catalogue, rendered output, and saved pane
identity. Cover failed credentials on restore and reconnect, corrected credentials
with explicit Retry, unavailable-host recovery, missing-session preservation,
an independently usable neighbouring pane, and shutdown during retries. Focused
tests cover diagnostic fragmentation and ambiguous/non-authentication failures.
## Delivery
- Implementation: GPT-5.6 Luna (`handoff_impl`). Adversarial review: GPT-5.6 Terra
(`handoff_review`), with independent integration and acceptance by root.
- Briefs specify `deps/zig/zig` and `ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache`.
- Opening and closing cleanup assessments, final frozen-source gates, real GUI
demonstration, and the retrospective are required before handoff. Recorded-demo
acceptance remains pending until the user gives it.
- Model selection and concrete review yield will be recorded. Token/cost figures
will be reported only if available.
The optional Linux real-SSH gate requires OpenSSH client, server, and keygen:
```sh
export ZIG_GLOBAL_CACHE_DIR=/tmp/muxg-zig-cache
deps/zig/zig build install native -Doptimize=ReleaseSafe -p dist/native-ssh-release
python3 test/native_ssh.py dist/native-ssh-release/bin/mux dist/native-ssh-release/bin/muxg
```
This complements the standard gates in the sprint workflow. It starts an owned
unprivileged loopback SSH server, with temporary keys and isolated state; local
socket/PTY permissions are required. `MUXG_VIDEODRIVER=wayland` selects a real
compositor for the same checks.
## Delivery evidence
Status: implemented, reviewed, and validated. Recorded-demo acceptance is pending.
The native tiling milestone remains accepted; this follow-up has its own demo gate.
Opening review found no justified standalone cleanup. Closing review kept the
classifier at the shared handoff boundary, removed an unused result category and
manual ASCII scan, and extracted the pump retry predicate. Review covered banner
false positives, authentication-method lists, initial and post-attachment retries,
deterministic test ordering, and cleanup of owned fixtures. A final correction
preserves all previous non-auth post-attachment retry behavior; the initial-open
whitelist was not extended to established connections.
Validation logs are under `/tmp/muxg-ssh-followup/`, with retained copies under
`/home/xanderle/.codex/visualizations/2026/09/05/01a071c2-5767-7790-a335-1a84536c9ca5/ssh-evidence/`:
- `before.log`: real-SSH negative control against the accepted prior release.
Rejected keys repeatedly spawned SSH while the pane remained reconnecting.
- `ci.log`: complete `make ci` passed. Following the final pump-only correction,
`make check` passed again (`check-final.log`); all affected native checks below
were rerun on the rebuilt final release.
- `build-final.log`: paired ReleaseSafe daemon/GUI and native unit tests passed.
`native-e2e-final.log`: the complete standard native integration gate passed.
- `real-ssh-final.log`: all five grouped real OpenSSH acceptance checks passed
offscreen. `demo-final.log`: the same checks passed on real Sway at 200%.
These use a real loopback SSH server and real daemon sessions, not an SSH stub.
Existing repository gates also passed; no new separate-VM validation is claimed.
- `scale.log`: real Wayland 200% → 100% → 150% → 200%, drag cancellation,
independently queried PTY dimensions, and framebuffer checks passed.
- `stress.log`, `stress-result.json`: `make native-stress` passed alone on the
isolated GPU compositor (NVIDIA GeForce RTX 3080, GLES2). Both binaries came
from the ReleaseSafe build graph. About 103.7 MB ran over 30.2 seconds;
sampled input-to-painted maxima were 55.4 ms on the shared daemon and 65.2 ms
on the separate daemon. Frame p99 was 18.1 ms. Input observations include
5 ms polling and are upper bounds; the recording is not a latency measurement.
The 30.4-second actual GUI demo is retained at
`/home/xanderle/.codex/visualizations/2026/09/05/01a071c2-5767-7790-a335-1a84536c9ca5/native-ssh-trial.mp4`.
It is 1100×700 H.264 with no audio, joining three continuous clips across window
restarts. It shows auth refusal with explicit Retry, correction and recovery,
post-attachment refusal, unavailable-host recovery, and missing-session refusal.
Use Ctrl+\\ then `p` to open recovery actions. No new ergonomic bindings were added.
Only a loopback demo server remains, serving the page and video on 18771 through
the [private demo route](https://charizard.folk-amberjack.ts.net/ssh-followup/).
After the initial automatic approval rejection, the user explicitly authorized
this publication ("Yes share"). HTTPS page (200) and video byte-range (206) checks
passed; evidence is in `/tmp/muxg-ssh-followup/sharing-check.json`. Existing routes
were preserved. Sharing approval does not imply recorded-demo acceptance.
The server PID is recorded in `/tmp/muxg-ssh-followup/server.pid`. Teardown removes
only `tailscale serve --https=443 --set-path /ssh-followup off`, then stops that
owned server. Scripts, clip metadata, and server source
remain in `/tmp/muxg-ssh-followup/`. Test daemons, SSH fixtures, and compositor were
stopped; final process checks found no remaining final-test fixture processes.
Canonical diagnostic parsing deliberately leaves unknown/localized/truncated text
recoverable. The shared `Reason` line limit remains 120 ASCII bytes. Model evidence,
available token counters, lessons, and the follow-up trigger are in RETRO.md.