Interactive Attach
cmdop connect opens an interactive shell on a remote CMDOP-registered
machine. There is no SSH key, no port to expose, and no IP to remember —
the relay handles transport, the resolver handles “which machine”, and the
session model handles disconnects.
The picker flow
Run cmdop connect with no argument from a TTY and you get an interactive
picker over the machines in the active workspace:
cmdop connectThe picker shows hostname, friendly name, online flag, and heartbeat age.
It honors Ctrl-C to cancel. After you select a machine, a confirm prompt
appears so a typo cannot drop you into the wrong shell. Select “yes” and
the attach loop begins.
The picker, confirm, and attach loop live in
internal/connect/picker/model.go and
go/cmd/cmdop/cmds/connect/connect_attach.go. They share one resolver
with every other Connect surface.
The picker is TTY-only. Running cmdop connect from a piped or
non-interactive context errors out and tells you to pass an explicit
hostname (or use --no-interactive).
Direct attach
If you already know which machine you want, skip the picker:
# By friendly name or hostname.
cmdop connect vps-audi
# By UUID prefix (or full UUID).
cmdop connect 8f23a4b0The string is run through machines.Resolve() — see
machines & identity for the precedence rules. If it matches
a unique machine, the attach starts immediately.
What you see on attach
A successful attach prints a banner then drops you into the remote shell:
$ cmdop connect prod-api-1
[connect] resolving prod-api-1 ... ok (id 8f23..., online 3s)
[connect] dialing relay ... ok
[connect] auth ... ok (session abcd1234)
[connect] attached — Ctrl-D to disconnect, Ctrl-C forwards as SIGINT
prod-api-1 $The banner is printed only after WaitReady() succeeds, so if you do not
see it, the dial or auth failed and you should read the error.
Ctrl-D vs Ctrl-C
Two control characters do different things:
Ctrl-D(byte0x04). Intercepted locally by the CLI. It does not reach the remote shell — it tells the CLI to close the stream. This means you cannot send EOF to a program running on the far side withCtrl-Dwhile attached. Pipe input via stdin or usecmdop connect execinstead.Ctrl-C(byte0x03). Forwarded verbatim to the remote shell as SIGINT. Use it the way you would in any normal shell — to cancel a long-running command.
This contract is enforced in internal/connect/client/attach.go. See
also client/CLAUDE.md §interactive attach contract.
If you need a literal Ctrl-D byte to reach the remote, use one-shot exec
with --stdin or run the program in a persistent session. Interactive
attach reserves Ctrl-D for local disconnect.
Window resize
Resize signals (SIGWINCH) are forwarded to the remote shell. Resizing
your terminal while attached re-runs ioctl(TIOCSWINSZ) on the remote
PTY, so tput, top, htop, and full-screen TUIs all behave normally.
Disconnect, reattach
A session is an object, not a connection. When you press Ctrl-D:
- The CLI sends a graceful close and exits.
- The remote PTY is not killed. The session enters a 30-second grace window during which any client can reattach.
- If nobody reattaches within the grace window, the session closes and the PTY is torn down.
Practical consequence: a flaky network or laptop sleep does not lose your shell as long as you reconnect within the grace period.
# Reattach to the same machine and pick up where you left off.
cmdop connect prod-api-1The CLI loops the picker on ErrUserDisconnected, so pressing Ctrl-D
inside the picker exits cleanly while pressing it inside an attached
shell drops you back to your previous prompt.
Multiple clients on one session
CMDOP sessions are not single-client. If you attach to a machine that already has an operator, you both see the same PTY:
- Both clients see all output (full ringbuf replay on join).
- Both clients can send input.
- Either client pressing
Ctrl-Donly disconnects that client; the session lives on.
The SESSION column in cmdop connect --list shows the operator that
opened the session, but extra observers are not enumerated. See
concepts/multi-client for the broader model.
Password-protected machines
If the remote agent has a machine password set (see auth-and-passwords), the attach flow gains an auth challenge:
[connect] auth ... password required
Password for prod-api-1:The CLI tries, in order:
- The
--passwordflag if you passed one. - The locally stored password from
internal/security/machinepw. - The
CMDOP_AGENT_PASSWORDenvironment variable. - A TTY prompt.
After a successful AuthSuccess, the session token is cached
process-wide for 24 hours so subsequent unary RPCs against the same
session do not re-prompt. See internal/connect/client/CLAUDE.md
§streaming vs unary auth.
Scripted, no-prompt attach
For CI and unattended automation, force the no-prompt path:
cmdop connect --no-interactive prod-api-1This disables the picker, the confirm step, and any TTY password prompt. If anything is missing (hostname, password, API key), the call fails fast with a structured error rather than blocking on a prompt.
--json implies --no-interactive and emits machine-readable output
suitable for piping into other tools. See exec for the
non-interactive workflow that is usually what you actually want for
scripts.
Common errors
| Error | Cause | Fix |
|---|---|---|
ambiguous machine "prod" | Prefix matches multiple hosts. | Type more characters or use the UUID. |
machine "vps-audi" is offline | Heartbeat older than the relay TTL. | Check the daemon on the target with cmdop agent status. |
Session requires password. Authenticate via ConnectTerminal first. | A unary RPC ran before streaming auth cached the session token. | Re-attach interactively once; the cache fixes it for the rest of the process lifetime. |
no API key for workspace "production" | Resolver bottomed out. | Run cmdop connect key set <key> or cmdop login. |
Related
Run a single command without holding an interactive PTY.
The streaming auth gate, password sources, and session token cache.
Persistent multi-command sessions for long-running work.
The resolver behind every cmdop connect <host> call.