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
- Lower `seconds` to 60 or less per wait step.
- Split longer delays across multiple steps or retries (e.g. a poll step that waits 30s then re-evaluates).
- If you control the SOP, model long waits as a scheduled resumption / cron trigger instead of an inline sleep.
- 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
- Lint authored SOPs: reject wait steps with seconds > 60 at authoring time, not runtime.
- Model long delays as scheduled resumption/cron triggers instead of inline sleeps.
- Cap any dynamic seconds value from context variables before it reaches the capability.
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
- capability '{}' requires authored `with` configuration to be
- invalid SOP name '{name}': must be a single path component (
- SOP rejected: {}
- candidate SOP did not validate as exactly one loadable SOP
- candidate manifest name '{}' does not match proposal target
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c1059dd986dbb693.
Report an issue: GitHub.