Skip to content

Output and Exit Codes

The JSON envelope, what goes to stdout versus stderr, and the exit codes scripts and agents branch on.

Updated September 3, 20262 min readPostsale CLI

The CLI is designed so machines can parse results without scraping progress text. Once you know where data goes and what exit codes mean, scripts and agents get much easier to trust.

In this article, we will separate stdout from stderr, read a postsale run envelope, use streaming mode safely, and recover from common exit codes.

Before We Begin

This article assumes that you have already run a successful command such as postsale account whoami or postsale carriers list.

The basic rule

StreamWhat belongs there
stdoutSuccess data: JSON, NDJSON, or one run envelope
stderrErrors, progress, tool breadcrumbs, confirmation prompts, spinner
exit codeOverall outcome class (success, auth, confirmation, network, and more)

There is no --json flag on postsale run. Non-stream run mode writes exactly one envelope to stdout (a contract the test suite enforces); the only exception is preflight failure, where stdout stays empty.

Direct commands

Here's how a typical read looks:

bash
postsale orders search --query "last week" > result.json

Pretty output for humans:

bash
postsale orders search --query "last week" --human

Stream every page as NDJSON:

bash
postsale orders search --query "last week" --all

On failure, stderr carries a structured envelope:

json
{
    "error": {
        "code": "...",
        "message": "...",
        "details": {},
        "request_id": "...",
        "retry_after": 12
    }
}

The envelope from postsale run

When the agent finishes (or hits a handled stop), stdout gets at most one JSON line: the completion envelope.

json
{
    "summary": "You have 3 carriers: USPS, FedEx, UPS.",
    "data": {},
    "tool_calls": [
        {
            "name": "get_carriers",
            "input": {},
            "output": [],
            "is_error": false,
            "duration_ms": 234
        }
    ],
    "turns": 2,
    "stop_reason": "end_turn",
    "provider": "anthropic",
    "model": "claude-opus-5"
}

You may also see:

  • warning such as max_turns_reached
  • simulated_effects / confirmed_effects (especially with dry-run)
  • On a safety gate: data shaped like { "blocked": true, "code", "tool", "gate", "next" }

Capture cleanly:

bash
postsale run "list my carriers" > envelope.json

Progress never belongs in that file.

When stdout is empty

Preflight failures (missing LLM credential, malformed configuration, unknown --continue id) produce empty stdout, a structured error on stderr, and a non-zero exit. Treat that combination as "no envelope was produced."

Exceptions:

  • postsale run --examples prints a bare JSON array of sample intents
  • --interactive and postsale continue emit one envelope per user turn

Streaming mode

bash
postsale run --stream "list my carriers" 2>/dev/null

Every stdout line is one NDJSON event with an event field (tool_call_start, tool_call_end, text, complete, and parallel batch events when used). Ctrl-C emits aborted and exits 130.

Exit codes you will actually use

CodeMeaningTypical next step
0SuccessParse stdout
64Usage or confirmation requiredFix flags; pass --yes when intentional
65Bad JSON inputFix the body
66Input file missingFix the path
75Temporary / network (doctor failed checks; warnings too under --pre-flight)Retry or fix warnings
78Configuration or allowlist refusalFix config
100AuthLogin or token
101Subscription inactiveHuman billing path
102ValidationRead error.details
103Rate limitedWait retry_after
104Not found (404)Verify the id with a search
106Request timeoutRetry or raise POSTSALE_HTTP_TIMEOUT_MS
130Stream abortedUser cancelled

Full table: Exit codes. Agent recovery by error.code: agent/errors.md.

Good to Know

  • Pipe or redirect stdout for data. Leave stderr visible while debugging.
  • Do not scrape spinner or breadcrumb lines as JSON.
  • Stream purity and non-stream envelope purity are enforced by CLI tests. Hosts can rely on them.

Additional Reading