SSH agents
Every Sox shell has one SSH_AUTH_SOCK, set once when the shell is born and
never changed: <home>/agent/<handle>.sock, where <home> is the session’s
Sox home (~/.sox on a remote host) and <handle> is the shell’s handle
(s0, s1, …). The shell’s own daemon serves that socket for as long as the
shell exists, so git push, ssh, and ssh-add -l inside the shell always
find something to talk to.
What answers on the other side is decided by who is attached. A Sox shell has no location of its own — only attaches do:
- The Mac app attaching locally brings the Mac’s agent: 1Password, Secretive,
or the built-in
ssh-agent, whicheversoxfound holding keys. - A terminal attaching over ssh with agent forwarding (
ssh -A, orForwardAgent yesin~/.ssh/config) brings the forwarded channel. - A terminal attaching over ssh without forwarding brings nothing.
When several clients are attached, the most recent attach wins. When that
client detaches, the shell falls back to the most recent of the others. When
no attached client brought an agent, the socket answers no identities until
one does: a signature in that shell fails with publickey denied rather than
hanging, and sox doctor reports the state as no_client.
Sox routes; it never chooses. Whether a signature needs Touch ID, a passphrase, or nothing at all is the agent’s policy, not Sox’s.
Local attach change: Sox only selects an agent that already lists keys.
If Apple’s launchd agent is empty at selection time, ssh-add inside
the Sox shell talks to the per-shell relay, not to launchd’s agent, and cannot
fill it. Load keys into the Mac’s agent outside the Sox shell. In the app,
start a new Sox session (or restart the app) to select it: new panes and
reconnects in an existing session reuse its cached selection, even after a
probe timeout. A fresh CLI sox attach selects again. The same applies to
keys loaded later by AddKeysToAgent. On-disk IdentityFile keys still work
without an agent upstream.
Checking a shell
sox doctor <host>:<project> --shell s0
The shells line for the handle ends with the agent columns, for example
agent=keyed agent_owner=per_handle_export agent_path=~/.sox/agent/s0.sock agent_target_kind=daemon_served agent_upstream=/tmp/ssh-abc123/agent.4242 identities=2. The states:
agent= | Meaning |
|---|---|
keyed | An agent answers with identities= keys. |
no_client | No attached client brought an agent. Attach from somewhere that has one. |
empty | The attached client’s agent holds no keys (ssh-add -l on that machine is empty). |
dangling | The shell’s socket is gone: its daemon is not running. sox doctor says which. |
unreachable | Something else sits at the path and does not speak the agent protocol. |
unknown | The shell was created by something other than Sox and inherited whatever it had. |
--json carries the same columns as rows[].agent on the shells step, for
scripts that want to assert on them before doing work in a shell.
Your ssh config and shell rc
Sox needs nothing in ~/.ssh/config beyond what you already use to reach the
host. Forwarding is per attach: a client that connects with ForwardAgent yes
for that host brings its agent, one without does not. Sox’s own sessions pass
-A when the session was started with --forward-agent.
Do not set SSH_AUTH_SOCK in ~/.zshrc, ~/.bashrc, or a direnv hook on
a host you reach through Sox: it replaces the per-shell socket with a fixed
path and every attach after the first is ignored. If a dotfile has to set it
for other logins, guard it:
[ -n "$SOX_HOME" ] || export SSH_AUTH_SOCK="$HOME/.1password/agent.sock"
A guard on SSH_CONNECTION is not enough. Sox-born shells on a Mac are
minted in the console session, so SSH_CONNECTION is unset even when
SOX_HOME is set. Sox restores the per-handle socket after rc; the
SOX_HOME guard keeps a later export from fighting that restore.
Sox exports SOX_HOME into every shell it creates, so the line is a no-op
there and unchanged everywhere else.