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

wait capability duration exceeds {MAX_WAIT_MS}ms

Error message

wait capability duration exceeds {MAX_WAIT_MS}ms

What it means

The built-in `wait` capability sleeps for `with.seconds` (clamped at >= 0, converted to ms) and enforces MAX_WAIT_MS = 60_000. A duration above 60 seconds bails before sleeping — long waits are rejected rather than blocking a capability worker for minutes.

Source

Thrown at crates/zeroclaw-runtime/src/sop/capability/builtins.rs:89

                    "waited_ms": { "type": "integer" }
                }
            })),
        }
    }

    fn execute(&self, _ctx: CapabilityContext, input: Value) -> Result<CapabilityResult> {
        let millis = input
            .get("milliseconds")
            .and_then(Value::as_u64)
            .or_else(|| {
                input
                    .get("seconds")
                    .and_then(Value::as_f64)
                    .map(|seconds| (seconds.max(0.0) * 1000.0) as u64)
            })
            .unwrap_or(0);
        if millis > MAX_WAIT_MS {
            bail!("wait capability duration exceeds {MAX_WAIT_MS}ms");
        }
        if millis > 0 {
            std::thread::sleep(Duration::from_millis(millis));
        }
        Ok(CapabilityResult::success(json!({ "waited_ms": millis })))
    }
}

struct ApprovalWaitCapability;

impl SopCapability for ApprovalWaitCapability {
    fn id(&self) -> &'static str {
        "approval.wait"
    }

    fn describe(&self) -> CapabilityInfo {
        let mut info = info(
            self.id(),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Lower `seconds` to 60 or less per wait step.
  2. Split longer delays across multiple steps or retries (e.g. a poll step that waits 30s then re-evaluates).
  3. If you control the SOP, model long waits as a scheduled resumption / cron trigger instead of an inline sleep.
  4. Validate authored durations at lint time so this fails at authoring, not runtime.

Example fix

# before
- uses: wait
  with:
    seconds: 300

# after
- uses: wait
  with:
    seconds: 60
Defensive patterns

Strategy: validation

Validate before calling

const MAX_WAIT_SECONDS: f64 = 60.0;
fn wait_ok(seconds: f64) -> bool {
    seconds.clamp(0.0, f64::MAX) * 1000.0 <= MAX_WAIT_SECONDS * 1000.0
}
assert!(wait_ok(step_with["seconds"].as_f64().unwrap_or(0.0)));

Type guard

fn is_wait_within_limit(with: &serde_json::Value) -> bool {
    with.get("seconds")
        .and_then(serde_json::Value::as_f64)
        .map(|s| s.max(0.0) * 1000.0 <= 60_000.0)
        .unwrap_or(true)
}

Try / catch

match capability_registry.execute("wait", &with).await {
    Err(e) if e.to_string().contains("exceeds 60000ms") => {
        eprintln!("split the wait into steps of <= 60s each");
    }
    rest => rest?,
}

Prevention

When it happens

Trigger: A SOP step `uses: wait` with `with: { seconds: 90 }` (or a computed/negative-then-huge value) — anything where seconds * 1000 > 60000 at execute() time.

Common situations: Porting workflow sleeps from another system that allows minutes; polling loops authored as one long wait; dynamic seconds from context/variables exceeding the cap; unit tests with exaggerated durations.

Related errors


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