zeroclaw-labs/zeroclaw · error · anyhow::Error

Gateway responded {status}: {err}

Error message

Gateway responded {status}: {err}

What it means

The CLI reached the gateway but `GET /admin/sop/pending` returned a non-2xx status; the message embeds the HTTP status code plus the gateway's `error` string from the JSON body (or 'request failed' when the body carries none). Connection-level failures raise a separate 'Failed to connect to gateway' error.

Source

Thrown at src/main.rs:6879

    let prefix = config.gateway.path_prefix.as_deref();
    let client = reqwest::Client::new();
    match cmd {
        SopCommands::Pending => {
            let url = gateway_admin_url(&host, port, prefix, "/admin/sop/pending");
            let resp = client
                .get(&url)
                .timeout(std::time::Duration::from_secs(5))
                .send()
                .await
                .map_err(|e| anyhow::Error::msg(format!("Failed to connect to gateway: {e}")))?;
            let status = resp.status();
            let body: serde_json::Value = resp.json().await.unwrap_or_default();
            if !status.is_success() {
                let err = body
                    .get("error")
                    .and_then(|v| v.as_str())
                    .unwrap_or("request failed");
                anyhow::bail!("Gateway responded {status}: {err}");
            }
            let pending = body
                .get("pending")
                .and_then(|p| p.as_array())
                .cloned()
                .unwrap_or_default();
            if pending.is_empty() {
                println!(
                    "{}",
                    t("cli-sop-pending-none", "No SOP runs waiting for approval.")
                );
            } else {
                println!(
                    "{}",
                    t("cli-sop-pending-header", "SOP runs waiting for approval:")
                );
                for r in pending {
                    let run_id = r.get("run_id").and_then(|v| v.as_str()).unwrap_or("?");

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Confirm the service on `[gateway]` host/port is the ZeroClaw daemon and is healthy (gateway logs / status output)
  2. Align `[gateway]` host, port, and path_prefix in the CLI config with the daemon's actual settings
  3. Re-provision or fix the admin credential if the status is 401/403
  4. Upgrade the daemon if it predates the `/admin/sop/*` routes (404 with a correct prefix)

Example fix

# before: daemon serves under a prefix, CLI config has none
[gateway]
host = "127.0.0.1"
port = 8080
# after
[gateway]
host = "127.0.0.1"
port = 8080
path_prefix = "/zeroclaw"
Defensive patterns

Strategy: try-catch

Validate before calling

# preflight: daemon reachable and admin routes present
curl -fsS "http://${ZC_GW_HOST}:${ZC_GW_PORT}${ZC_GW_PREFIX}/admin/sop/pending" >/dev/null \
  || echo "gateway preflight failed" >&2

Try / catch

if ! out="$(zeroclaw sop pending 2>&1)"; then
  case "$out" in
    *"Failed to connect"*) echo "daemon unreachable"; exit 2 ;;                 # transport
    *"Gateway responded 5"*) retry_with_backoff zeroclaw sop pending ;;          # server-side
    *"Gateway responded 4"*) echo "$out"; exit 1 ;;                             # config/auth: do not retry
  esac
fi

Prevention

When it happens

Trigger: `zeroclaw sop pending` when the gateway returns 401/403 (admin authentication), 404 (`[gateway]` path_prefix mismatch or a daemon older than the SOP admin routes), or 5xx (daemon-side failure).

Common situations: The `[gateway]` block pointing at the wrong port or service; a path_prefix configured on the daemon but not the CLI (or vice versa); an admin credential expired or never provisioned; running a pre-SOP daemon version.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/45bb881ea0850615. Report an issue: GitHub.