{"record":{"id":"e46a7aca529aecb4","repo":"zeroclaw-labs/zeroclaw","slug":"webhook-audit-e","errorCode":null,"errorMessage":"webhook-audit: {e}","messagePattern":"webhook-audit: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/hooks/builtin/webhook_audit.rs","lineNumber":133,"sourceCode":"                    .with_attrs(::serde_json::json!({\"hook\": \"webhook-audit\"})),\n                \"webhook-audit hook is enabled but no URL is configured — audit events will be dropped\"\n            );\n        }\n\n        // Validate URL against SSRF if one is provided.\n        if !config.url.is_empty()\n            && let Err(e) = validate_webhook_url(&config.url)\n        {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(\n                        ::serde_json::json!({\"hook\": \"webhook-audit\", \"error\": format!(\"{}\", e)})\n                    ),\n                \"webhook URL validation failed\"\n            );\n            panic!(\"webhook-audit: {e}\");\n        }\n\n        let client = reqwest::Client::builder()\n            .timeout(Duration::from_secs(5))\n            .build()\n            .expect(\"failed to build webhook HTTP client\");\n        Self {\n            config,\n            client,\n            pending_args: Arc::new(Mutex::new(HashMap::new())),\n        }\n    }\n}\n\n/// Simple glob matching: `*` matches any sequence of characters.\nfn glob_matches(pattern: &str, text: &str) -> bool {\n    if pattern == \"*\" {\n        return true;","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/hooks/builtin/webhook_audit.rs#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n$ zeroclaw agents delete writer\nError: 2 live ACP session(s) for `writer` — end them first\n\n# after\n$ # 1. close the ACP clients (editor/TUI) using that agent\n$ # 2. stop the zeroclaw gateway (it owns the /acp WebSocket sessions)\n$ zeroclaw agents delete writer","handlingStrategy":"validation","validationCode":"use zeroclaw_gateway::agent_owned_state::live_acp_session_count;\n\n// Run the same gate the CLI precheck uses BEFORE attempting the delete.\nfn agent_safe_to_delete(config: &Config, alias: &str) -> anyhow::Result<bool> {\n    Ok(live_acp_session_count(config, alias)? == 0)\n}","typeGuard":null,"tryCatchPattern":"match alias_delete(&config, &alias).await {\n    Err(e) if e.to_string().contains(\"live ACP session(s)\") => {\n        // Refusal, not a crash: surface it, have the operator close ACP\n        // clients / stop the gateway, then retry. Never delete session\n        // state by hand to force the delete through.\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["acp","agent-alias","delete","cli","fail-closed","websocket-sessions"],"backgroundTag":"resource-in-use","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}