arc network answers one question: can this machine reach the outside world the way arc needs to? It has a single subcommand, doctor, which runs four checks — proxy discovery, DNS, an HTTPS request, and a WebSocket handshake — and reports each on its own line. They are reported separately on purpose: "DNS resolves but the proxy refuses CONNECT" and "nothing resolves at all" are different faults with different fixes, and a single pass/fail would hide which one you have.
Captured against arc 2.0.0-beta.48 (commit 5a5316bde, 2026-09-10). The version number alone does not pin this — 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 above before trusting anything below byte-for-byte; checks and flags can move between releases regardless.
Usage
arc network doctor [options]Global flags (see Overview): --json, --view, --instance / -i (which local ARC instance; omit for default), and --home (instance root; to pick which instance, use --instance). doctor does not talk to a daemon, so it works whether or not one is running.
--target <url>: HTTPS URL to probe (defaulthttps://main.abtnetwork.io/)--ws-target <url>: WSS URL to probe (defaultwss://main.abtnetwork.io/api/websocket?vsn=2.0.0)
arc network on its own is not a command: it prints Please specify a subcommand along with the subcommand list, and exits 5.
Example (everything reachable, no proxy)
$ arc network doctor
Outbound network doctor
Proxy:
(none)
Checks:
✓ proxy-discovery no proxy environment variables
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✓ https-through-proxy HTTP 200 direct
✓ wss-through-proxy WSS handshake direct
OKAll four passing exits 0. The two network checks keep through-proxy in their names even when no proxy is involved; the detail column is what tells you which route was actually taken — direct here.
The address on the direct-dns line is whatever your own resolver returns, so expect a different one, and on a network with a captive or filtering resolver expect an address the name does not really have. This is worth more than a footnote, because the layer a failure gets attributed to follows the resolver: a name that does not exist is reported as a DNS failure where the resolver says so, and as a connection or TLS failure where the resolver answers anyway. When a doctor result surprises you, confirm the name independently before believing which check went red.
Example (a proxy is configured but not answering)
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 arc network doctor
ERROR: Outbound network doctor
Proxy:
HTTP_PROXY http://127.0.0.1:9/
HTTPS_PROXY http://127.0.0.1:9/
Checks:
✓ proxy-discovery proxy environment variables present (credentials redacted)
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✗ https-through-proxy PROXY_CONNECT_FAILED connect ECONNREFUSED 127.0.0.1:9
✗ wss-through-proxy PROXY_CONNECT_FAILED connect ECONNREFUSED 127.0.0.1:9
FAILEDAny failing check exits 5. The ERROR: prefix on the first line is the CLI's one sole marker for "this run failed" (arc#6301) — every failure the CLI reports, from any command, starts a message this same way, so a caller can find the front of an error without knowing which command produced it. Notice what stayed green: proxy discovery passed because it only reports which variables are set, not whether the proxy works, and DNS passed because resolution never went through the proxy at all. The two that failed name the reason — PROXY_CONNECT_FAILED — instead of a generic timeout, which is what tells you to look at the proxy rather than at DNS or at the target host. Proxy URLs are printed with any credentials redacted.
Machine-readable output
--json prints the same run as structured data. Below is the failing run above, because failure is where the interesting fields appear: each failed check gains a code, and required / affectsOverallHealth say which results the verdict is computed from. direct-dns flips to required: false here — once a proxy is in play, resolving directly is no longer something the verdict depends on.
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 arc network doctor --json
ERROR: {
"proxy": {
"httpProxy": "http://127.0.0.1:9/",
"httpsProxy": "http://127.0.0.1:9/"
},
"checks": [
{
"name": "proxy-discovery",
"ok": true,
"status": "pass",
"required": true,
"affectsOverallHealth": true,
"detail": "proxy environment variables present (credentials redacted)"
},
{
"name": "direct-dns",
"ok": true,
"status": "pass",
"required": false,
"affectsOverallHealth": false,
"detail": "main.abtnetwork.io → 198.18.0.38"
},
{
"name": "https-through-proxy",
"ok": false,
"status": "fail",
"required": true,
"affectsOverallHealth": true,
"code": "PROXY_CONNECT_FAILED",
"detail": "connect ECONNREFUSED 127.0.0.1:9"
},
{
"name": "wss-through-proxy",
"ok": false,
"status": "fail",
"required": true,
"affectsOverallHealth": true,
"code": "PROXY_CONNECT_FAILED",
"detail": "connect ECONNREFUSED 127.0.0.1:9"
}
],
"ok": false
}The leading ERROR: above is real and not a copy-paste mistake: on a failing run the same one-sink prefix from the human example lands in front of the JSON too, on the same line as the opening {, because a failed command's whole message — human text or JSON, this CLI does not distinguish at that point — gets the marker prepended once. Strip that one token before parsing on a non-zero exit, or route on the exit code instead of trying to parse stderr as JSON at all. A clean run is the same shape with the code fields absent, every status set to pass, proxy an empty object, and the top-level ok true — and no ERROR: prefix, because it only appears on failure. One more thing worth knowing before you pipe this: on a failing run stdout is 0 bytes — the whole message, prefix and all, goes to stderr — so arc network doctor --json | jq sees nothing at all rather than an error object; check the exit code first, or you will end up debugging your jq filter instead of the actual failure.
The two targets move independently. --target retargets the HTTPS probe and the DNS check with it — probing https://arc.arcblock.io/ puts arc.arcblock.io → … on the direct-dns line instead of the default host — while --ws-target moves only the WebSocket probe, leaving DNS on whatever --target names. Aiming --ws-target at a closed port therefore fails exactly one check:
$ arc network doctor --ws-target wss://127.0.0.1:9/
ERROR: Outbound network doctor
Proxy:
(none)
Checks:
✓ proxy-discovery no proxy environment variables
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✓ https-through-proxy HTTP 200 direct
✗ wss-through-proxy UPSTREAM_REJECTED
FAILEDA literal address and a closed port were chosen here so the probe fails the same way on every machine — no name to resolve means no resolver to disagree. What names the failed layer is the code, UPSTREAM_REJECTED, distinct from PROXY_CONNECT_FAILED / DIRECT_DNS_FAILED / TLS_FAILED; the free-text detail after it is not guaranteed and came back empty in this capture — an immediately-refused loopback connection did not leave the underlying socket layer anything to say. Do not match on the detail text; match on the code, in --json if you need it structured. Use these two flags when the defaults are reachable but the host you actually care about is not.