zeroclaw-labs/zeroclaw · error · anyhow::Error
Gateway responded {status}: {detail}
Error message
Gateway responded {status}: {detail} What it means
A POST to `/admin/sop/approve` or `/admin/sop/deny` came back non-2xx. SOP routes put a typed `outcome` label in the body (e.g. `not_waiting` maps to 404, `rejected_self_approval` to 403); the CLI prefers `outcome` over `error` so the operator sees the precise reason.
Source
Thrown at src/main.rs:6975
.map_err(|e| anyhow::Error::msg(format!("Failed to connect to gateway: {e}")))?;
let status = resp.status();
let out: serde_json::Value = resp.json().await.unwrap_or_default();
if status.is_success() {
println!(
"{}",
out.get("outcome").and_then(|v| v.as_str()).unwrap_or("ok")
);
Ok(())
} else {
// Non-2xx bodies from the SOP routes carry the typed `outcome` label
// (e.g. not_waiting -> 404, rejected_self_approval -> 403), not `error`;
// prefer it so the operator sees why, falling back to `error`.
let detail = out
.get("outcome")
.and_then(|v| v.as_str())
.or_else(|| out.get("error").and_then(|v| v.as_str()))
.unwrap_or("request failed");
anyhow::bail!("Gateway responded {status}: {detail}");
}
}
#[cfg(feature = "agent-runtime")]
enum PaircodeAction {
/// GET the current code; do not mint or revoke anything.
Show,
/// Issue a fresh code for an additional client; revoke nothing.
AddClient,
/// Revoke every paired token + clear the registry, then issue a code.
RotateAll,
/// Revoke a single device's token, then issue a code.
RotateDevice(String),
}
#[cfg(feature = "agent-runtime")]
impl PaircodeAction {
/// True when the action mints a new code (POST), false for `Show` (GET).View on GitHub (pinned to 88bb9c8533)
Solutions
- Re-list waiting runs with `zeroclaw sop pending` and pass a run_id that is still waiting
- Treat `not_waiting` immediately after your own successful approve as idempotent completion, not a failure
- For `rejected_self_approval`, have a different approver act per the configured approval policy
- For 5xx, check the daemon logs before retrying
Example fix
# before zeroclaw sop approve run-123 # run already decided elsewhere # after zeroclaw sop pending zeroclaw sop approve <run_id from the still-waiting list>
Defensive patterns
Strategy: try-catch
Validate before calling
# refresh the waiting list right before deciding
zeroclaw sop pending | grep -q "$run_id" || { echo "run $run_id is not waiting"; exit 2; } Try / catch
if ! zeroclaw sop approve "$run_id" 2>/tmp/sop_err; then
case "$(cat /tmp/sop_err)" in
*not_waiting*) echo "already decided; treating as done" ;; # idempotent success
*rejected_self_approval*) assign_second_approver "$run_id" ;;
*"Gateway responded 5"*) retry_after_backoff ;;
*) cat /tmp/sop_err; exit 1 ;;
esac
fi Prevention
- Always re-fetch `sop pending` immediately before approve/deny
- Make approval scripts idempotent on `not_waiting` instead of failing
- Route self-approval-restricted runs to a second operator by design
When it happens
Trigger: `zeroclaw sop approve <run_id>` for a run that already finished or was decided (`not_waiting`), approving your own run while self-approval is rejected (`rejected_self_approval`), an unknown run_id (404), or a daemon-side 5xx.
Common situations: Two operators racing to decide the same SOP run; scripts retrying an approve that already succeeded; the run being denied or timing out between `sop pending` and `sop approve`.
Related errors
- Gateway responded {status}: {err}
- `zeroclaw sop approve/deny/pending` requires the agent-runti
- gateway.path_prefix contains invalid character '{bad}'; only
- Embedding API error {status}: {text}
- Node request failed: {} {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c48a5056dd91c58a.
Report an issue: GitHub.