0 or 1. You come here when you point the CLI somewhere else — a dev deployment, a stack you run yourself — or when you script it, isolate it in CI, or hand it an identity instead of a browser sign-in.
Which Receipt the CLI talks to
Every command that reaches a server resolves two URLs first: the gateway, which serves/connect/*, the MCP endpoint and the ingest endpoint, and the sign-in origin, the web origin that hosts the browser approval page. On the hosted app both are the same origin. Trailing slashes are stripped from either.
Not every command accepts every flag.
setup, login and doctor take --target, --server-url and --auth-url. receipt connect status|<connector>|disconnect take --server-url and --auth-url plus a positional target word or URL. receipt connect list|tools|call take --server-url and --target, and no --auth-url — they never sign in. tools and mcp take --server-url as a gateway override, plus --target to pick which saved session to send. workspace takes --target and no URL override. import and observe take --target to choose which saved session uploads.
The prod chain
First non-empty value wins. Forprod only, a value whose host is localhost, 127.0.0.1 or ::1 is discarded — and discarding it skips the rest of the environment chain rather than handing the turn to the next variable, so resolution continues at step 4. The sign-in chain filters local hosts the same way.
With nothing at all — which in practice means a source checkout rather than a release build — you get:
The dev chain
Same shape with aDEV prefix — RECEIPT_CONNECT_DEV_GATEWAY_URL, RECEIPT_CONNECT_DEV_SERVER_URL, RECEIPT_CONNECT_DEV_URL, then RECEIPT_CONNECT_DEV_URL and RECEIPT_DEV_APP_URL when --auth-url was not passed — with two differences: local hosts are not filtered, and there is no default. Unconfigured, --target dev fails with receipt connect dev URL is not configured. Set RECEIPT_CONNECT_DEV_GATEWAY_URL or RECEIPT_CONNECT_DEV_URL.
The local chain
Gateway:RECEIPT_CONNECT_LOCAL_SERVER_URL, RECEIPT_CONNECT_GATEWAY_URL, RECEIPT_PROXY_SERVER_URL, else http://127.0.0.1: plus RECEIPT_PORT, PORT, or 8787.
The released binary reads no
.env file of any kind. --target local therefore means 127.0.0.1:8787 and 127.0.0.1:3000 unless you export the variables in the shell you run it from. If your stack listens elsewhere, export RECEIPT_CONNECT_LOCAL_SERVER_URL and RECEIPT_CONNECT_LOCAL_AUTH_URL before running receipt doctor --target local.The sign-in origin
--auth-url always wins. After that:
Because the hosted default only ever reaches the sign-in origin through that last resort, the hosted app has to serve both the sign-in page and
/connect/* on one origin — and it does. Point the CLI at exactly the origin your browser is signed in to: the approval page redirects to /auth/sign-in whenever the request carries no session for that origin.
How two URLs are compared
Deciding whether a saved session belongs to the host you just resolved is one comparison used everywhere: both sides are parsed,localhost is rewritten to 127.0.0.1, trailing slashes are dropped, and the results compared. Scheme and port stay significant. http://localhost:8787 matches http://127.0.0.1:8787/, but not https://localhost:8787 and not port 8788.
Where the session is stored
Every write goes to a temporary file at mode
0600 and is then renamed into place, so the file is never half-written and never group-readable. What each file contains — including the plaintext bearer token — is on Sign in: setup, login, and logout.
When a command asks for one of the three named targets, the matching per-target file is read first, and the active file only if its own target normalizes to the same name. Ask for a URL instead and no per-target file exists, so whatever the active file holds is used. A file that will not parse, or that is missing its gateway URL, token or organization id, reads as no session rather than as an error — but a file you cannot read for another reason, a permissions problem for instance, throws and exits 1.
Expiry is decided locally: the CLI decodes the token’s payload and compares its expiry with the clock. It never verifies the signature, and a payload it cannot read is treated as live. There is no refresh — after the 12 hours, setup, login and the connect family re-run the browser login, while workspace, tools and mcp send the dead token and fail on the gateway’s 401: unauthorized from the workspace, connection-list and MCP routes, and Receipt could not authenticate this task. Its runtime access token must be renewed before retrying. from /connect/tools and /connect/call.
Environment variables
Behaviour and storage
Endpoint resolution
RECEIPT_CONNECT_PUBLIC_GATEWAY_URL, RECEIPT_CONNECT_WORKER_GATEWAY_URL, RECEIPT_CONNECT_PUBLIC_URL, RECEIPT_PUBLIC_BASE_URL, RECEIPT_CONNECT_PROD_GATEWAY_URL, RECEIPT_CONNECT_PROD_SERVER_URL, RECEIPT_CONNECT_PROD_URL, RECEIPT_CONNECT_PROD_AUTH_URL, RECEIPT_CONNECT_URL, RECEIPT_APP_URL, RECEIPT_CONNECT_DEV_GATEWAY_URL, RECEIPT_CONNECT_DEV_SERVER_URL, RECEIPT_CONNECT_DEV_URL, RECEIPT_CONNECT_DEV_AUTH_URL, RECEIPT_DEV_APP_URL, RECEIPT_CONNECT_LOCAL_SERVER_URL, RECEIPT_CONNECT_LOCAL_AUTH_URL, RECEIPT_AUTH_URL, BETTER_AUTH_URL, VITE_BETTER_AUTH_URL, RECEIPT_CONNECT_GATEWAY_URL, RECEIPT_PROXY_SERVER_URL, RECEIPT_PORT, PORT, WEB_PORT — all as ordered in the chains above.
Identity without a browser
Precedence is not uniform, and this is the trap:
receipt connect list|tools|call— the environment wins outright. The saved session is read only whenRECEIPT_CONNECT_TOKENis unset, and then only if the gateway host matches. Setting the token without a gateway variable fails withReceipt Connect gateway is unavailable; run 'receipt setup' or set RECEIPT_CONNECT_TOKENeven though a perfectly good session exists.receipt toolsandreceipt mcp serve— the saved session wins, and the environment is the fallback.--server-urlhere replaces the gateway while still sending the session token, with no host check; see Tools and MCP.receipt workspace, andreceipt connect status|<connector>|disconnect— no environment fallback at all. They require a saved session.
setup and login also set RECEIPT_CONNECT_GATEWAY_URL, RECEIPT_CONNECT_PUBLIC_GATEWAY_URL, RECEIPT_CONNECT_TOKEN, RECEIPT_CONNECT_ORGANIZATION_ID, RECEIPT_CONNECT_WORKSPACE_ID, RECEIPT_CONNECT_WORKSPACE_NAME and RECEIPT_CONNECT_USER_ID in their own process, and only where you have not set them yourself. Nothing reaches your shell.
Companion and client tools
CODEX_HOME locates the Codex configuration for receipt mcp install|status|remove. RECEIPT_CLAUDE_PROXY_BIN and CLAUDEN_BIN override the Claude observer companion binary, which is otherwise looked for beside the CLI itself and then on PATH.
Not read
No.env file, and no shell configuration of any kind. START_ALL_*, RECEIPT_CLI_LOAD_LOCAL_ENV and RECEIPT_CLI_NO_FORCE_EXIT belong to the developer CLI, not this binary. ZERO_UPSTREAM_DB is read but has no effect: the released binary always uses the local or cloud receipt store, never Postgres.
Running in CI or a sandbox
Give the run an identity and a private config directory, and stop a stray command from opening a browser:receipt connect list|tools|call reads RECEIPT_CONNECT_GATEWAY_URL, while the prod chain that doctor, setup and login resolve reads RECEIPT_CONNECT_PUBLIC_GATEWAY_URL. With gateway and token both set, receipt connect list|tools|call never touches the config directory at all, and receipt tools falls back to the same variables when no session file exists. Gate the job on receipt doctor --json and its exit code rather than on parsing text.
Exit codes
There is no third code from the program itself. Two you may still see come from outside it:
137, with no output. macOS killed the binary before it ran — a quarantined copy, or, far more rarely, a published build whose own ad-hoc signature is malformed. Reinstall with the published installer (Install the CLI); CLI troubleshooting tells the two apart and gives thexattrfix.127from the developer CLI, withreceipt CLI requires Bun. Set RECEIPT_BUN_BIN or install Bun on PATH.— that is the in-repo shell wrapper, not the released binary.
Starting Receipt Connect sign-in at <authUrl>..., Step 1 of 2: sign in to Receipt and Saved Receipt session was rejected by the gateway; signing in again., so parse the --json forms, never the text. Standard error carries only the message a failing command ends with, plus claude observer failed to start: <message> when observe cannot spawn the companion binary.
Output shapes
--json means three different things depending on where it appears:
The envelopes:
On
receipt tools, ok: true means the gateway answered — not that the provider succeeded. A provider-side failure comes back inside the result as isError content.tools takes --output or --output-file, writes the envelope atomically at mode 0600, and prints {ok: true, outputFile, bytes} instead. mcp config takes the same two flags and the same atomic write, but prints receipt mcp config: wrote <path> — or, with --json, its usual envelope carrying the path in output. import and observe take --output-file only, write it plainly at your umask, and print the same {ok: true, outputFile, bytes} pointer. doctor, setup, login, workspace and the connect commands have no output-file flag.
Next step: look up an error string and its fix.