Runs a shell command on a remote host over SSH (action: "exec", the default), or moves a file with SCP (action: "scp"). Both credential styles work out of the box: inline host/username/password/key_path for a one-off, or a named connection from ssh_hosts.json for a host you visit repeatedly.
Success is exit_status == 0. Any other status returns success: false with the full output preserved in the error — read it, it is the remote command's own stderr, not a Navi failure.
| Parameter | Required | Description |
|---|---|---|
action |
no | exec (default) or scp. |
command |
for exec |
Shell command to run on the remote host. |
host |
for a direct connection | Hostname or IP. |
username |
no | SSH user. Defaults to $USER on the Navi host, so pass it for anything but the obvious case. |
password |
no | Password auth. |
port |
no | Default 22. |
key_path |
no | Private key, e.g. ~/.ssh/id_rsa. ~ is expanded. |
connection |
no | Named entry from ssh_hosts.json. |
local_path / remote_path / direction |
for scp |
upload (local→remote) or download (remote→local). |
timeout |
no | Seconds, default 60. |
background |
no | Detach; returns a task_id immediately. See Detaching. |
Inline — anything ad-hoc:
{"command": "uptime", "host": "192.168.1.168", "username": "ubuntu"}
{"command": "df -h /", "host": "10.0.0.5", "username": "root", "key_path": "~/.ssh/id_ed25519"}
{"command": "whoami", "host": "10.0.0.5", "username": "root", "password": "..."}
Named connection — the host is in ssh_hosts.json. Pass connection instead of the host triple:
{"command": "systemctl status navi", "connection": "prod"}
connection takes precedence, and inline host/username/password/port/key_path override the stored values one by one — useful for the same host under a different user.
With neither a connection that exists nor a host, the call fails with No SSH target specified before any network attempt.
key_path (or client_keys in ssh_hosts.json) is expanded and used first; a password given alongside it becomes a fallback.~/.ssh/.known_hosts:
"known_hosts": null (as in the template) → the system ~/.ssh/known_hosts is used;null) in its ssh_hosts.json entry. Skipping is fine for a fresh VPS and wrong for production.A connection is reused within a session (keyed by session + host + port + user, 20-minute TTL). Two sessions touching the same server keep separate pools and never interfere. If the server drops the connection (DisconnectError, ConnectionLost, EOF) the entry is evicted and the command is retried once on a fresh connection; a second failure is returned as-is. PermissionDenied is never retried — fix the credentials instead.
{"action": "scp", "direction": "upload", "connection": "prod",
"local_path": "/tmp/build.tar.gz", "remote_path": "/opt/app/build.tar.gz"}
{"action": "scp", "direction": "download", "connection": "prod",
"remote_path": "/var/log/navi.log", "local_path": "/tmp/navi.log"}
Both paths are required; the result says Uploaded: or Downloaded: with both ends. SCP honours the same timeout (default 60 s) — raise it for a large file.
Pass "background": true for builds, package installs, backups and anything else that outlives the turn. You get a task_id back immediately and the result arrives as a note at the start of a later turn; inspect it with the tasks tool. When you detach without passing timeout, the executor raises the limit to 300 s for that run — do not leave a detached command at the 60 s default and assume it finished.
username and landing as the Navi host's user, not the intended one.command (ssh root@10.0.0.5 uptime) — that depends on local SSH config and key forwarding. Use host/username, or connection.exit_status as a broken tool. The tool worked; the command failed.scp without direction — it defaults to upload, so a download silently needs it.