The SSH backend's real-host tests skipped on every CI run (nothing set
TERMINAL_SSH_HOST), and every Docker backend test mocked subprocess or the
docker CLI, so no CI run had executed either backend end to end.
- remote-backends.yml: starts a throwaway sshd container on 127.0.0.1 with a
per-run keypair (scripts/ci/start_test_sshd.sh) and runs
test_ssh_environment.py plus the new test_docker_backend_real.py against it
and the runner's Docker daemon with a digest-pinned debian:12-slim. A file
that skips fails the job, so the lane cannot go green over zero coverage.
- New classifier lane `remote_backends` (tools/environments/, tools/terminal_tool*,
the real tests, the sshd harness, run_tests.sh). ci.yaml calls the workflow
only when it fires, plus every push to main; required_results makes a selected
lane mandatory, like the update consumers.
- The real tests read HERMES_TEST_SSH_* / HERMES_TEST_DOCKER_IMAGE (forwarded
by run_tests.sh), because the hermetic conftest blanks TERMINAL_SSH_KEY as a
credential before any test can read it.
- New real SSH cases: exit code/stderr, cwd persistence with spaces vs per-call
workdir, quoting across the ssh hop, SendEnv passthrough through a real
AcceptEnv, file tools write/read on the remote.
- New real Docker cases: exec + exit codes, cwd vs workdir, exported env
persistence, docker_env/docker_forward_env reach the container and
nothing else does, quoting, file tools write/read, /root surviving a
container replacement.
- The existing one-shot test is xfail(strict): TERMINAL_SSH_PERSISTENT=false
is read into ssh_config["persistent"] but SSHEnvironment never consumes it,
so every SSH session persists exported env. Its _cleanup also cleaned the
raw task id, not the resolved env key, so one test's env leaked into the next.