Flitzdocs
CLI

Exit codes and messages

The six-code exit contract for script authors, the message convention, verbose output, and the secret-free bug-report capture.

Every flitz failure is reported the same way and branches on the same small set of exit codes. This page is the one place a script author needs.

Exit codes

flitz reports exactly six codes, so a script can branch on the class of failure without matching prose:

CodeClassMeaning
0SuccessThe command did what it was asked.
1Usage or configurationThe invocation, or the project/machine/organization state it names, is wrong and the fix is yours: an unknown command or flag, a missing or malformed argument, --json on a command that does not support it, a required confirmation that could not be obtained non-interactively, a missing or malformed pin, an unsupported host, or an unsatisfiable plugin resolution.
2General or unexpectedThe catch-all: a local filesystem or permission failure, an unresolvable home directory, a lock that could not be acquired, or an interrupted command.
3Not authorizedNo valid credential, a rejected or expired one, an inactive organization license, or an interactive-only operation attempted under an API key.
4Service or networkThe service or a link was unreachable, timed out, returned a persistent error, or returned a malformed response; an upload failed; or a write-once destination was already occupied.
5BuildAny failure of the build pipeline — kernel compilation, bytecode compilation, or packaging — whichever step produced it.

A build failure is code 5 whichever step produced it; the failing tool is named in the message, not encoded in the status. See Troubleshooting for what each build failure means.

Two subtleties around publish:

  • Code 2 is the only code that can follow a publish whose uploads all succeeded. Once the URLs are printed, a later local failure — a --qr-output PNG that could not be written — reports it and exits 2 without retracting the result.
  • A publish whose bundle upload succeeded but whose landing-page upload did not is partially successful and exits 4: the failure message includes the bundle URL and emits no deeplink or page URL.

Reading the code in a script

if flitz status --json > status.json; then
  jq -r '.checks[] | select(.verdict=="blocking") | .message' status.json
fi

flitz publish --json > result.json
case $? in
  0) deploy "$(jq -r .bundle_url result.json)" ;;
  3) echo "not authorized — check FLITZ_APIKEY" >&2 ;;
  5) echo "build failed — see the log above" >&2 ;;
  *) echo "publish failed (exit $?)" >&2 ;;
esac

A --json command writes no JSON on failure. Check the exit status first, then parse stdout only on 0. flitz status is the exception that always exits 0 — its verdicts live in the check markers, not the exit status.

Message convention

Errors and warnings are written to stderr in one shape: the verdict marker and a cause, then one indented line per next action.

✗ <cause, a lowercase sentence naming the concrete value involved>
  → <the next command to run>

In plain output the marker is its ASCII form:

[fail] <cause>
  -> <next command>

The rules the CLI holds to, and that you can rely on:

  • The cause names the concrete value that matters — the tag, the path, the host, the HTTP status. It never contains a Dart type name and never the word "error:". The marker already says it failed.
  • A hint names the next thing to run. A cause with no useful next step carries none — an unhelpful hint is worse than none.
  • Compiler output passes through verbatim. The raw stdout/stderr of flutter build bundle and dart2bytecode is printed unreformatted beneath a framing line naming the tool; the CLI does not parse, re-wrap, or summarize it.
  • Secrets are redacted everywhere. Credentials, the API key, plugin tokens, and signed upload URLs are replaced in causes, in -v output, in the bug-report capture, and in passed-through tool output. A signed URL keeps its origin and path with its query replaced by ?<redacted>; before a captured compiler command line is echoed, every --dart-define value is replaced with <redacted> while the key is kept, because defines routinely carry API keys.

Warnings use the marker in the same shape and never change the exit code.

Verbose output (-v)

-v (or --verbose) has exactly one level and three effects:

  1. Stack traces on error — every error additionally prints its Dart stack trace beneath the cause.
  2. Live build outputflutter build bundle and dart2bytecode stream their output to stderr as it arrives, instead of being captured and only quoted on failure. A long compile stops looking like a frozen spinner.
  3. Retry announcements — a retried network request says so.

That is all. -v does not log HTTP requests, does not dump argument vectors, does not print environment variables, and does not enable a log file. A second -v (-vv) is a usage error — there is only one level.

Without -v, build-tool output is captured: discarded on success, printed verbatim beneath the error on failure (with --dart-define values redacted).

The bug-report capture

When the CLI hits something it does not recognize — and only then — it prints

✗ unexpected error — please report this
  <exception type>: <message>

followed by a delimited capture block, and invites you to include it wherever you are reporting the problem. A recognized, cleanly handled error never emits one; the block exists precisely because the CLI does not know what went wrong.

The block is built from an allowlist — it contains exactly these facts and nothing swept from your environment or filesystem:

FieldContents
CLIVersion and host triple.
OSOperating system and version.
ProjectThe pinned tag (or that none is pinned), whether it is installed, and how many SDKs are cached.
InvocationThe command line with subcommand path and flag names kept, and every flag value replaced by <redacted> except a fixed allowlist of non-sensitive ones (paths, tags, target platform, URL scheme, shell name).
AuthBooleans only: whether an interactive credential is present, whether FLITZ_APIKEY is set, and the last known license verdict. Never a token, never an identity.
ErrorThe exception type, its message, and the stack trace.
Output tailThe last few lines of captured build-tool output, bounded.

It never contains the contents of credentials.json, config.yaml, ~/.netrc, or gradle.properties; never a request or response header; never a signed URL. A safety-net pass runs over the whole assembled block and replaces anything credential-shaped — Bearer/ApiKey values, long high-entropy runs, and any URL's query — as a backstop for the one field whose content the CLI does not author.

The block is written to stderr only, is never saved to a file, and is never transmitted anywhere. It is safe to paste as-is into a bug report.

Interrupting a command

Ctrl-C is handled. The CLI writes one line naming the phase it interrupted, terminates any build tool it started rather than orphaning it, deletes its scratch directory, releases any lock it holds, and exits 2. A resumable SDK download partial is deliberately kept, so re-running continues rather than restarting. If the interrupt landed during an upload, the notice adds that a partial object may remain and that re-running publish allocates a fresh one. A second Ctrl-C leaves immediately, still exit 2.

On this page