CLI

This page lists every senro command, the conventions they all share, and the exit codes a script can rely on. Full usage, flags, and behavior for each command live on the three pages linked below.

git clone https://github.com/xavidop/senro
cd senro
go build -o senro ./cmd/senro

senro supports Linux and macOS. Windows is not supported; see Attach security for why.

Every command

CommandWhat it doesDetail
senro run <pkg>Build a pipeline package, exec it, attach and renderRun and watch
senro attachWatch a live run, or replay a finished one from diskRun and watch
senro uiServe a browser view of a live run on loopbackRun and watch
senro shellOpen a session inside a live run’s stepRun and watch
senro runsList runs under ./runs, newest firstWorkspaces and runs
senro cache gcReclaim disk in the local content-addressed storeCache and verify
senro cache explainWhy a Pure() step hit or missed the action cacheCache and verify
senro cache scratchList the scratch entries in the shared bucketCache and verify
senro verify --recheck-pureRe-run cached Pure() steps and compare their digestsCache and verify
senro ws lsList a run’s workspaces, or one workspace’s filesWorkspaces and runs
senro ws pullWrite a workspace’s stored body out to a directoryWorkspaces and runs
senro ws diffCompare two runs’ workspaces from their stored indexesWorkspaces and runs
senro logs fetchBring an archived run back from the shared cacheWorkspaces and runs
senro func checkReport cgo in a Func step’s dependency graphWorkspaces and runs
senro helpPrint the full synopsis to stdout and exit 0

Conventions

Four things hold across the whole CLI.

Help and version. senro help, senro -h, and senro --help print the full command list to stdout and exit 0.

Subcommands don’t repeat that help. senro run --help fails with senro run: unknown flag "--help". senro attach --help, senro shell --help, and senro ui --help each print their own flag list, but to stderr. All three exit 2.

There’s no senro version or --version flag. Both fail with senro: unknown command, exit 2.

A run ID looks like 20260812T151058-540c8ca44b. It’s a UTC timestamp plus a short random suffix. This is also the directory name under runs/.

Naming a run. Any command that takes a run accepts a run ID, a path to a run directory, or nothing at all. Leave it out and senro uses the newest directory under ./runs. Don’t have an ID yet? senro runs lists what’s there.

senro logs fetch is the exception. Its RUN argument names a key in the shared store, not anything on your machine, so a path is refused there.

Credentials never come from a flag. A TCP attach server’s bearer token comes from $SENRO_ATTACH_TOKEN, never --token. A flag value would show up in ps(1) output for every user on the machine, and in shell history.

TLS connections always verify against the system’s root certificates. There’s no --insecure flag. If you need a private CA, set $SSL_CERT_FILE or $SSL_CERT_DIR instead. See Attach security.

Choosing a renderer: --ui

senro run and senro attach both take --ui=auto|tui|plain|none, defaulting to auto.

ValueWhat you get
autoThe terminal UI on a TTY, plain streaming lines otherwise
tuiThe terminal UI. A hard error on a non-TTY, never a silent downgrade
plainOne line per event, no escape sequences
noneNo rendering at all; the exit code is still the run’s

If you pass --ui=tui without a real terminal, senro fails with senro: --ui=tui requires a terminal, but stdout is not a TTY. This is intentional: in a CI log, the TUI’s escape sequences would look like garbage, or worse, like a run that succeeded when it didn’t. See The TUI.

Exit codes

These exit codes are a stable contract. A script wrapping senro can depend on these values meaning exactly this. Each code covers more than just “the run failed” though, so check the value, not a specific cause.

CodeMeaning
0Success
1The run failed, or one of the other causes below
2Usage error, or one of the other causes below
78No trigger matched the event (EX_CONFIG): nothing to run
130Cancelled (Ctrl-C, or an external SIGINT/SIGTERM)

senro ws diff and senro verify always exit 0, whether or not they find anything. A finding is an answer, not a failure.

Besides a failed run, exit 1 also covers:

  • func check found cgo
  • verify --fail-on-mismatch found a step that did not reproduce its cached result
  • cache gc failed
  • ws ls could not load an index
  • ws pull refused a tar entry that escapes its destination
  • logs fetch could not reach the shared store, or was handed an object that did not match its digest
  • a write to stdout failed
  • an attach watch errored
  • the pipeline process was killed by a signal

Besides a usage error, exit 2 also covers:

  • go build of the pipeline package failed
  • the pipeline process would not start
  • the attach socket would not connect
  • a cache record or workspace index is missing
  • ws pull or logs fetch found a non-empty destination without --force
  • ws diff could not compare a workspace
  • logs fetch found no shared cache configured, no such run in the store, or credentials the store refused
  • func check’s own analysis failed to run

About 78

Exit 78 is neither success nor failure. It means the pipeline was asked whether an event was its business, and it said no. A dispatcher can tell this apart from a real success or failure without parsing any output.

senro itself never makes this decision. The pipeline binary decides whether an event matches, and senro run passes its exit code through unchanged. On a 78, senro also prints one line, senro run: no trigger matched the event, so there is nothing to run (exit 78). Without that line, a bare exit 78 would look like a crash. See Triggers.

About detaching

Detaching (pressing q in the TUI) is not a failure in senro attach. Detaching doesn’t stop the run, so the exit code reflects the run’s actual outcome. If the run hasn’t finished yet when you detach, the exit code is 0.

senro run works differently, because it owns the pipeline process. It waits for that process to exit and reports its exit code; --ui=none behaves the same way. If you want to walk away without waiting, start the pipeline binary yourself and watch it separately with senro attach.

Where to go next