Skip to main content

Exit Codes

Every arc command reports its outcome as an exit code — 0 for success, plus seven distinct non-zero values (one reserved but not yet produced) — so a script can tell what kind of failure it hit…

Every arc command exits with a number you can branch on. Having more than one non-zero value is the whole point: "the path you asked for is not there", "you mistyped the command", and "no daemon is running" want three different responses from a script, and nothing should have to pattern-match on English error text to tell them apart.

Exit codeMeaningScope
0The command did what you askedevery command
1Not there — or, for service start, already thereevery command
2Permission deniedreserved — no command in this release returns it (see below)
3The instance is on record but is not running (stopped, starting)arc service status
4The instance is recorded as running, but its process is gone (dead)arc service status
5Runtime error, including every usage mistakeevery command
6The command needed a daemon and none was runningevery command
7--view llm was requested on a command that has no llm rendererany command's --view llm

Mind the scope column. 3 and 4 above are arc service status's own reading of an instance record, not a promise about the whole CLI: elsewhere those two numbers carry the runtime's generic meanings — 3 a conflict, 4 a partial success — and 4 has a live producer, a batch arc did issue in which some providers succeeded and others failed. A script that reads rc == 4 as "the daemon died" will misread that batch. Branch on 4 only for arc service status.

2 is in the enum but has no producer in this release — the 2 section below shows how that was checked, not assumed. 7 is scoped narrowly: it only fires for --view llm on the commands that lack an llm renderer; every command still accepts --view default / --view json / --view human.

Each section below is a real run of the situation it describes, captured against arc 2.0.0-beta.48 (commit 5a5316bde, 2026-09-10). The version number alone will not tell you which build you have — 2.0.0-beta.48 has named more than one binary this cycle — so run arc --version and compare its third field, the commit, against the one printed here before trusting any output on this page byte-for-byte. Read the table as "what these codes mean" rather than as the complete set of numbers arc can ever return: a command that fails in some way not listed here still exits non-zero, and new codes can appear in later releases.

0 — the command did what you asked

bash
$ arc service list
NAME          ID                PORT   STATUS   HOME                          SOURCES
(no instances)

An empty result is still success. Finding nothing to list is not a failure — asking for a specific thing that is not there is what gets you 1.

1 — not there, or already there

Reading a path that does not exist:

bash
$ arc afs read /no-such-file --instance notes
ERROR: No data found for path: /no-such-file

Naming an instance that was never created:

bash
$ arc service status --instance nope
ERROR: no instance named "nope". `arc service list` shows 1

And — the case worth remembering — starting an instance that is already up:

bash
$ arc service start --instance notes
ERROR: instance "notes" is already running
       pid 93652, port 61430, started 15s ago
       use `arc service restart --instance notes` or pick another name

That last one is 1, not 5. The code means "what you named was not in the state you needed", which covers both a thing that is missing and a thing that is already there. It is not a usage mistake, so it is not 5.

2 — permission denied, defined but not yet produced

The runtime's exit-code enum reserves 2 for PermissionDeniedError, and that class exists in the CLI's own error module. Nothing in this release throws it: no command constructs a PermissionDeniedError, and a permission failure raised by the AFS layer underneath the CLI is not translated into one — it just keeps propagating as an ordinary error, which lands on 5 like any other unhandled failure. This is checked, not assumed: an unreadable file, made with chmod 000, reproduces the real behavior directly.

bash
$ arc afs read /vault/locked.txt --standalone
ERROR: EACCES: permission denied, open '/tmp/arc-perm-test/secret/locked.txt'

That command exits 5. Treat 2 as a number the protocol has set aside for a distinction the CLI does not draw today, not as one you will see in practice.

3 — the instance exists but is not running

bash
$ arc service stop --instance notes
Service stopped: notes
$ arc service status --instance notes
  Instance: notes
  ID:       ab5aa97074c454a0
  Status:   stopped
  PID:      93652
  Port:     61430
  URL:      http://127.0.0.1:61430
  Home:     /tmp/arc-docs-demo/notes
  Note:     PID / Port / URL above are the LAST KNOWN values from this instance's previous run, not a live process.

Several fields — Version, Commit, Checkout, Space, Sources, Host and Started — are elided above. The pid, port and URL are still reported after a stop, and the Note: line says in words that they are historical — in --json the same fact is a lastKnown marker.

starting returns 3 as well, which matters if you are writing a wait-for-up loop: 3 means "not ready", not "not coming". On a cold start the window is a few seconds, and a status poll walks stoppedstarting (exit 3) → up (exit 0). Treat 3 as "keep waiting" and 4 as "stop waiting".

4 — recorded as running, but the process is gone

No ordinary sequence of commands gets you here, because this code describes damage rather than a choice. To see it, start an instance and kill its daemon outright, so the registry row still claims up while the process behind it no longer exists:

start prints a full status block of its own; the pid you need for the next step is on its PID: line. Both blocks below are trimmed to the fields that matter here.

bash
$ arc service start --instance notes --home /tmp/arc-docs-demo/notes
  Instance: notes
  Status:   up
  PID:      95288
  Port:     61430
  Home:     /tmp/arc-docs-demo/notes
$ kill -9 95288
$ arc service status --instance notes
  Instance: notes
  ID:       ab5aa97074c454a0
  Status:   dead
  PID:      95288
  Port:     61430
  URL:      http://127.0.0.1:61430
  Home:     /tmp/arc-docs-demo/notes
  Note:     PID / Port / URL above are the LAST KNOWN values from this instance's previous run, not a live process.

dead is what you get from a crash, an OOM kill, or a reboot — anything that ends the process without going through arc service stop. The distinction from 3 is worth having: stopped is a state someone chose, dead is one nobody chose. arc service gc removes records in this state and only this state.

5 — runtime error, usage mistakes included

This is the broadest code. Every way of getting the command line wrong lands here. Each of the next three also prints the relevant usage text after what you see below — the full command list for the first, that command's own help for the other two. An unknown command:

bash
$ arc serivce list
ERROR: Unknown command: "serivce"

Did you mean?
  arc serve
  arc service

an unknown option:

bash
$ arc service list --bogus
ERROR: Unknown argument: bogus

a missing required argument:

bash
$ arc space rm
ERROR: Not enough non-option arguments: got 0, need at least 2

and, past the command line, a request the runtime cannot route:

bash
$ arc afs exec /no-such-action --instance notes
ERROR: No module found for path: /no-such-action in namespace 'default'

5 also covers a command that ran correctly and reported a real-world problem — arc network doctor exits 5 when any of its checks fails. So 5 on its own does not tell you whether you typed something wrong or something out there is broken; that is what the message is for.

6 — the command needed a daemon and none was running

bash
$ arc afs ls /
ERROR: No AFS daemon is running for instance "default".
Start it with:  arc service start
Or use --standalone for ad-hoc mode (no runtime state)

The suggested command on the second line echoes back only the flags you actually typed — omit --instance and --home and it suggests the bare arc service start; pass --instance foo --home ~/foo yourself and it suggests exactly that back, never a value you did not type. 6 exists so this case is distinguishable from 5 without reading the message. An unmet precondition you can fix by starting a daemon is a different thing from a command that failed, and the error text names the fix. The same 6 comes back for a named instance whose daemon is stopped or dead, not only for default.

7--view llm was requested, but this command cannot render it

--view llm is accepted globally, but not every command has an llm renderer for it, and an unimplemented one fails closed instead of silently falling back to the default view. arc network doctor has no llm renderer:

bash
$ arc network doctor --view llm
ERROR: --view llm is not implemented for `network doctor`. This command has no llm renderer (declare ⇒ execute; will not silently fall back to default).

arc afs ls does implement one, and the same flag on a command that supports it succeeds:

bash
$ arc afs ls / --standalone --view llm
ENTRY /spaces CHILDREN=-1 DESC="Local DID Space logical roots"
ENTRY /peers CHILDREN=-1 DESC="Dispatchable ARC hosts (always includes local)"
ENTRY /ash CHILDREN=-1 DESC="ASH pipeline DSL for deterministic data pipelines"
ENTRY /dev CHILDREN=-1
ENTRY /modules CHILDREN=-1
ENTRY /team CHILDREN=-1
ENTRY /.knowledge KIND=afs:system CHILDREN=-1 DESC="Provider capability index"
ENTRY /.meta KIND=afs:system CHILDREN=-1 DESC="Root metadata and mount info"
ENTRY /.actions KIND=afs:system CHILDREN=-1 DESC="Root-level executable actions"
TOTAL 9

The distinction that matters for scripts: an unrecognized --view value (say, --view bogus) is a usage mistake caught by argument parsing, and exits 5 like any other bad flag —

bash
$ arc service list --view bogus
ERROR: Invalid values:
  Argument: view, Given: "bogus", Choices: "default", "llm", "human", "json"

— while --view llm on a command with no llm renderer is a valid, recognized value that this particular command cannot honor, and exits 7. Without the split, a script cannot tell "I typed the flag wrong" from "the flag is right but this command doesn't support it yet" without parsing English text either way.