zeroclaw-labs/zeroclaw · warning

update --check failed: {}

Error message

update --check failed: {}

What it means

The gateway version check re-spawns the current executable as `zeroclaw update --check --json` and surfaces the subprocess's trimmed stderr verbatim when it exits non-zero. The real cause is in that stderr text — commonly network failure, an invalid `--version` argument, or an environment where the self-update check cannot run. The HTTP handler degrades gracefully (returns 200 with an `error` field), so this surfaces as a warning, not a failed request.

Source

Thrown at crates/zeroclaw-gateway/src/version.rs:218

}

async fn run_cli_check(version: Option<&str>) -> anyhow::Result<CliCheck> {
    let exe = std::env::current_exe().context("cannot determine current executable path")?;
    let mut cmd = tokio::process::Command::new(exe);
    cmd.arg("update").arg("--check").arg("--json");
    if let Some(v) = version {
        cmd.arg("--version").arg(v);
    }
    cmd.stdin(std::process::Stdio::null());

    let output = tokio::time::timeout(CHECK_TIMEOUT, cmd.output())
        .await
        .context("version check timed out")?
        .context("failed to spawn `zeroclaw update --check`")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("update --check failed: {}", stderr.trim());
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    serde_json::from_str::<CliCheck>(stdout.trim())
        .context("failed to parse `update --check --json` output")
}

/// `GET /api/version/check[?force=true][&version=X]`
///
/// Never fails the dashboard: on any error it returns 200 with
/// `{ is_newer: false, error }` so the version tag degrades gracefully.
pub async fn handle_version_check(
    State(state): State<AppState>,
    headers: HeaderMap,
    Query(q): Query<CheckQuery>,
) -> impl IntoResponse {
    if let Err(e) = require_auth(&state, &headers) {
        return e.into_response();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Run `zeroclaw update --check --json` manually and read the stderr reproduced in the message
  2. If a `version` query param is set, pass a valid semver string or omit it entirely
  3. Ensure outbound network/proxy access for the update check; the dashboard degrades gracefully, so this is non-urgent

Example fix

# before
GET /api/version/check?version=latest
# after
GET /api/version/check
GET /api/version/check?version=0.7.2
Defensive patterns

Strategy: fallback

Validate before calling

// Validate a caller-supplied version before it reaches the check:
fn is_semver_like(v: &str) -> bool {
    !v.is_empty() && v.chars().next().is_some_and(|c| c.is_ascii_digit())
      && v.split('.').count() >= 2
}

Try / catch

let info = match run_cli_check(version).await {
    Ok(c) => c,
    Err(e) => {
        // Dashboard contract: never fail the request; degrade to a warning payload.
        return VersionCheckInfo { is_newer: false, error: Some(e.to_string()), ..Default::default() };
    }
};

Prevention

When it happens

Trigger: `GET /api/version/check` with `force=true` or a `version` query param that busts the cache, while `zeroclaw update --check` fails: offline machine, proxy blocking the release host, or a non-semver `version` value the update command cannot parse.

Common situations: Air-gapped or proxied environments; passing `?version=latest` instead of a semver string; running a dev build whose self-update metadata is missing.

Related errors


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