Skip to content
Subnomic
← Back to blog
CLI Sep 11, 2026 · 3 min read

Only the URL on stdout

A small contract in the Subnomic CLI, and why one friendly line would break every pipeline that relies on it.

Here is the most important line in a deploy job:

URL=$(subnomic sites deploy --prod)

It only works if the command prints exactly one thing to standard output — the URL. Everything else, from hashing to the upload bar, goes to standard error.

stdout is a contract

Output meant for a person and output meant for a program travel on different streams for a reason. The moment a CLI prints “Deploying… done!” to stdout, every script that captures it ends up with a progress log in a variable. That is why a friendly extra line on stdout in subnomic sites deploy would be a breaking change, not a cosmetic one.

Pretty on a terminal, plain in a pipe

The login form, pickers, spinners and upload bar appear only when stdin and stderr are both terminals, and they draw on stderr. Redirect either one and every command falls back to plain tab-separated tables on stdout and plain progress on stderr — and a picker that would have asked a question refuses instead of waiting forever for an answer that isn’t coming.

$ subnomic servers | awk '{print $1}'
$ subnomic sites ls --json

Structured output is opt-in, never the default. Colour honours NO_COLOR, decided per stream. Cancelling a picker exits with status 130 — the shell’s own interrupt code — rather than pretending an error happened.

CI without a login

A runner has no browser, so it authenticates with a workspace API key in SUBNOMIC_TOKEN. Give a deploy job only the sites.deploy scope: it can publish and roll back, and it cannot delete the site it publishes to.

- run: curl -fsSL https://get.subnomic.com | bash
- id: deploy
  run: echo "url=$(subnomic sites deploy --prod)" >> "$GITHUB_OUTPUT"
  env:
    SUBNOMIC_TOKEN: ${{ secrets.SUBNOMIC_TOKEN }}
    SUBNOMIC_API: https://api.subnomic.com

Small promises

None of this is clever. It is a handful of rules — one stream for results and one for people, refuse instead of hang, opt in to structure — kept consistently, so that the pipeline you write today keeps working.

Every command is in the CLI reference.

Enjoyed this? Read more on the Subnomic blog.