zeroclaw-labs/zeroclaw · error
webhook-audit
Error message
webhook-audit: {e} What it means
Raised by agent_delete_precheck (src/alias_cli/mod.rs:454), the hard gate that runs before `zeroclaw agents delete <alias>` mutates any config or owned state. It opens the data_dir-backed AcpSessionStore (crates/zeroclaw-gateway/src/agent_owned_state.rs:13) and counts live Agent Client Protocol sessions owned by the alias; a count > 0 aborts the delete because removing the alias would orphan live WebSocket sessions on the gateway's `/acp` route. The check mirrors the gateway's own delete gate and fails closed: if the session store cannot even be read, deletion is also refused (context error "could not verify live ACP sessions"). Nothing has been modified when this error surfaces.
Solutions
- Disconnect the ACP clients using that agent (close the editor/TUI windows or tabs holding the session), then re-run `zeroclaw agents delete <alias>`
- If no client is visibly connected, stop the running zeroclaw gateway process (it owns the `/acp` WebSocket sessions), then retry the delete
- For scripted deletes, always stop or drain the gateway first so no `/acp` sessions can be live
- If it still refuses, inspect the ACP session store under data_dir for live rows owned by the alias and report stale rows as a bug rather than deleting state by hand
Example fix
# before $ zeroclaw agents delete writer Error: 2 live ACP session(s) for `writer` — end them first # after $ # 1. close the ACP clients (editor/TUI) using that agent $ # 2. stop the zeroclaw gateway (it owns the /acp WebSocket sessions) $ zeroclaw agents delete writer
Defensive patterns
Strategy: validation
Validate before calling
use zeroclaw_gateway::agent_owned_state::live_acp_session_count;
// Run the same gate the CLI precheck uses BEFORE attempting the delete.
fn agent_safe_to_delete(config: &Config, alias: &str) -> anyhow::Result<bool> {
Ok(live_acp_session_count(config, alias)? == 0)
} Try / catch
match alias_delete(&config, &alias).await {
Err(e) if e.to_string().contains("live ACP session(s)") => {
// Refusal, not a crash: surface it, have the operator close ACP
// clients / stop the gateway, then retry. Never delete session
// state by hand to force the delete through.
}
other => other,
} Prevention
- Close all ACP clients (editor/TUI integrations) for an agent before deleting it
- Run scripted agent deletions while the gateway is stopped or drained of /acp sessions
- Treat an unreadable session store as a refusal too — the precheck intentionally fails closed
- Remember the gateway's own delete API has the same gate, so both paths need the same sequencing
When it happens
Trigger: Running `zeroclaw agents delete <alias>` (after confirming/--yes) while an ACP client — an editor or TUI integration — holds an open `/acp` WebSocket session attributed to that alias, typically with the zeroclaw gateway running. Any state where count_live_sessions_by_agent(alias) returns > 0, including session rows not yet marked ended after an abrupt client exit while the gateway is still up.
Common situations: Deleting an agent from one terminal while an editor integration still has the agent's session open in another; automated cleanup scripts that run against a live gateway with connected ACP clients; stale live-session rows after a crashed client when the gateway process is still alive.
Related errors
- Unknown agent (no [agents. ] entry configured)
- ACP elicitation/create failed
- ACP elicitation/create (multi) failed
- ACP elicitation/create (multi) timed out after
- ACP elicitation/create timed out after
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/e46a7aca529aecb4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/zeroclaw-runtime/src/hooks/builtin/webhook_audit.rs:133
.with_attrs(::serde_json::json!({"hook": "webhook-audit"})),
"webhook-audit hook is enabled but no URL is configured — audit events will be dropped"
);
}
// Validate URL against SSRF if one is provided.
if !config.url.is_empty()
&& let Err(e) = validate_webhook_url(&config.url)
{
::zeroclaw_log::record!(
ERROR,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(
::serde_json::json!({"hook": "webhook-audit", "error": format!("{}", e)})
),
"webhook URL validation failed"
);
panic!("webhook-audit: {e}");
}
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()
.expect("failed to build webhook HTTP client");
Self {
config,
client,
pending_args: Arc::new(Mutex::new(HashMap::new())),
}
}
}
/// Simple glob matching: `*` matches any sequence of characters.
fn glob_matches(pattern: &str, text: &str) -> bool {
if pattern == "*" {
return true;View on GitHub (pinned to 88bb9c8533)