The v2-only daemon rejected v1 frames, so deploying it would brick every v1 client in the field. main() now dispatches on the FIRST byte: 0x00 = v2 (path unchanged); an ASCII digit = a new STREAMING v1-legacy parser (that digit is the first byte of the first netstring length, pushed back via io.MultiReader before parsing); anything else = exit 1 with a diagnostic. v1 wire (what the deployed cmd/sic sends, no preamble): netstring(mode "exec"|"sh") + argv/command netstrings + the empty-netstring terminator 0:, ; everything after the terminator is the child's stdin. exec runs argv directly (each netstring is ONE argv element, never space-split); sh runs via sh -c. Reuses pumpStdin so EPIPE / reap / exit-status semantics match the v2 path exactly. Removed the sicd_test.go "old-v1-frame-without-preamble expects exit 1" subtest: it pinned the v2-only REJECTION of v1, which dual-read deliberately reverses. v1 acceptance is now covered by dualread_test.go (9 cases: exec/sh, argv boundaries, trailing stdin, exit status, and the four reject paths). Implemented by noether (rich agent) after the room hit a genuine wall — DeepSeek couldn't build the parser in 10 attempts, Kimi-k2.6 soliloquy-stalled past the 300s per-call timeout. All green: go test ./... ok, 27/27 Python reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sic
Run a command on a remote host without a shell re-parsing its arguments.
ssh host touch 'a b' creates two files: ssh joins its arguments with spaces
and hands the result to the remote login shell, which splits it again. sic host touch 'a b' creates one file named a b. sic sends the argument vector to a
daemon on the host as length-prefixed fields, and the daemon calls execvp on
it. No shell parses the arguments.
Components
sic— client. Takes argv on the command line, frames it as netstrings, pipes the frame oversshtosicd.sicd— daemon on the target host. Reads netstrings from stdin and runs the command.
Both are single static Go binaries (cmd/sic, cmd/sicd). A Python reference
implementation of the daemon is in gateway/sicd.
Wire format
Each field is a netstring: <length>:<bytes>,. A frame is a mode field, zero or
more argument fields, and an empty terminator:
<mode> <arg>* 0:,
argv = ["touch", "a b"] is 4:exec,5:touch,3:a b,0:,. The length prefix means
no byte needs escaping and no delimiter can collide with the payload.
| mode | selector | behaviour |
|---|---|---|
exec (default) |
4:exec, |
execvp(argv), no shell. Arguments are passed through unchanged. |
sh |
2:sh, |
one field passed to sh -c. Pipes, redirects, and globs work. |
Usage
sic <host> <command> [arg ...]
sic --sh <host> '<shell string>'
sic <host> -- <command> [arg ...] # explicit separator
sic host1 echo hello world
sic host1 touch 'a b' # one file named "a b"
sic host1 echo '$HOME' # literal, no expansion
sic --sh host1 'echo hi | wc -c'
Build
go build -o bin/sic ./cmd/sic
go build -o bin/sicd ./cmd/sicd
Install
Client:
cp bin/sic ~/bin/sic
Daemon on each target host. sicd runs what it is sent, so it must only be
reachable over an authenticated transport. Pin it to a dedicated ssh key so the
target can run nothing else with that key:
scp bin/sicd host:/tmp/sicd
ssh host 'sudo install -m755 /tmp/sicd /usr/local/bin/sicd'
# ~/.ssh/authorized_keys on the target:
# command="sicd",no-port-forwarding,no-pty ssh-ed25519 AAAA...
Do not listen on a TCP or message-bus port.
Test
printf '4:exec,2:id,0:,' | ssh host sicd
sic host echo hello world
See also
docs/design.md— wire format, exec modes, transport and security, prior art.demos/quoting-hell.py— six cases run both ways, plainsshversussic.SKILL.md,SKILL-bg.md— agent skill definitions (foreground and background).