GitHub - kmeinke/ssh-cast: A minimal, self-contained pair of Go binaries to record SSH sessions and watch them live or replay them. No server-side install: everything runs on your local box.

GitHub

4 min read Original article ↗

A minimal, self-contained pair of Go binaries to record SSH sessions and watch them live or replay them later. No server-side component: everything runs on your local box, under ~/.ssh-watch/.

  • ssh-cast — drop-in wrapper around ssh that records the session.
  • ssh-watch — lists recorded sessions and attaches to a live one or replays it.

Why

We started giving AI coding agents direct SSH access to real machines — staging boxes, build servers, sometimes production. That's useful, but an agent driving a shell is not the same as a human driving one: it can run for a long time unattended, it can go off-script, and when something goes wrong there's no one at the keyboard who watched it happen.

ssh-cast exists so that every agent-initiated SSH session leaves a recording, and so a human can tail it live instead of finding out after the fact. Point an agent's ssh calls at ssh-cast instead, and you get:

  • Live observabilityssh-watch can attach to a running session and watch the agent's terminal output in real time, the same way you'd watch a tmux pane over someone's shoulder.
  • An audit trail — every session is saved as a standard asciinema v2 .cast file, so you can replay exactly what an agent typed and saw, after the fact, at normal speed or fast-forwarded.
  • No behavior change for the agent — it's a thin wrapper around the real ssh binary running under a pty, so keys, ssh_config, and known_hosts all behave exactly as they would if the agent called ssh directly. The agent doesn't need to know it's being recorded.

This is intentionally narrow: it captures the PTY byte stream, not structured commands, and it only sees sessions actually launched through it (an agent using its own SSH library instead of the ssh binary bypasses it entirely — see Design constraints). It's an observability tool, not a sandbox or a security boundary.

Build

Requires Go 1.21+. Dependencies are vendored, so builds don't need network access.

go build -o bin/ssh-cast  ./cmd/ssh-cast
go build -o bin/ssh-watch ./cmd/ssh-watch

Packaging (.deb)

make build          # cross-compiles both binaries for GOOS=linux (ARCH=amd64 by default)
make deb            # build + package into dist/ssh-cast_<version>_<arch>.deb
make deb ARCH=arm64 # override target arch
make clean

Makefile builds always target GOOS=linux, since both binaries only make sense on a system with /usr/bin/ssh. Requires dpkg-deb on the build host (Debian/Ubuntu, or a container/CI image with dpkg-dev) — not available for local iteration on a non-Linux dev box. If you're on Windows, run it from a Linux environment (WSL, a container) rather than a Windows-mounted path; dpkg-deb rejects the permission bits that DrvFs mounts report.

Usage

Recording a session

Use ssh-cast exactly like ssh — same arguments, same behavior:

ssh-cast user@host
ssh-cast user@host "uptime"

Point an agent's SSH invocations at ssh-cast (e.g. alias ssh to it, or configure the agent's SSH command) and every session it opens gets recorded automatically.

Watching and replaying

ssh-watch                 # list sessions (● marks ones currently live)
ssh-watch <session>       # attach live if running, else replay the recording
ssh-watch -r <session>    # force replay with original timing
ssh-watch -R <session>    # replay as fast as possible, no timing waits

On disk

Each ssh-cast invocation creates ~/.ssh-watch/<timestamp>-<pid>/:

  • meta.json — target host/user, start/end time, pid, and whether the session is still live.
  • session.cast — the recording, in asciinema v2 format.
  • live.sock — a local unix socket, present only while the session runs.

Nothing here is network-exposed; the live socket is unix-domain and local-only by design.

Design constraints

  • Wraps the ssh binary, not a library — it only sees sessions launched through it. Not a security control.
  • Captures the raw PTY stream only; no command-level parsing or structured events yet.
  • No webhook/notification hooks yet (planned, not implemented).