{"record":{"id":"3e8c2f6e031163dc","repo":"zeroclaw-labs/zeroclaw","slug":"acpchannel-listen-is-not-supported-free-form-ask","errorCode":null,"errorMessage":"AcpChannel.listen is not supported (free-form ask_user awaits ACP elicitation Phase 2)","messagePattern":"AcpChannel\\.listen is not supported \\(free-form ask_user awaits ACP elicitation Phase 2\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":308,"sourceCode":"            .notify(\n                \"session/update\",\n                json!({\n                    \"sessionId\": self.session_id,\n                    \"update\": {\n                        \"sessionUpdate\": \"agent_message_chunk\",\n                        \"content\": {\n                            \"type\": \"text\",\n                            \"text\": message.content,\n                        }\n                    }\n                }),\n            )\n            .await;\n        Ok(())\n    }\n\n    async fn listen(&self, _tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {\n        anyhow::bail!(\n            \"AcpChannel.listen is not supported (free-form ask_user awaits ACP elicitation Phase 2)\"\n        )\n    }\n\n    fn supports_free_form_ask(&self) -> bool {\n        false\n    }\n\n    async fn add_reaction(\n        &self,\n        _channel_id: &str,\n        _message_id: &str,\n        _emoji: &str,\n    ) -> anyhow::Result<()> {\n        // ACP renders agent output as message chunks — there's no per-message\n        // reaction primitive in the protocol, so silently no-oping (the trait\n        // default) would falsely report success to the agent. Surface as Err\n        // so the `reaction` tool's caller sees the truth.","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L290-L326","documentation":"AcpChannel deliberately does not implement inbound listening: ACP is a client-driven back-channel where the client pushes requests to the agent, and free-form ask_user support is deferred to elicitation Phase 2. listen() always fails fast instead of pretending to work, so orchestrators that blindly start listeners on every registered channel see the refusal immediately rather than a silent no-op.","triggerScenarios":"A generic channel supervisor iterates the channel map and calls listen(tx) on each channel — including the per-session AcpChannel registered at session/new. Any code path that treats AcpChannel like a poll-style channel (Slack/Bluesky style) hits this.","commonSituations":"Adding a new channel kind to a runtime that assumes all channels are bidirectional listeners; porting a supervisor loop from another channel type without a capability check; wiring the ACP channel into the wrong registry.","solutions":["Skip listen() for ACP channels — gate on supports_free_form_ask() (which returns false for ACP) or name() == \"acp\" before spawning a listener task.","Drive ACP inbound traffic from the RPC request handlers (elicitation, permissions) instead of a listener task.","If you need user input over ACP today, use request_choice (multiple-choice) until elicitation Phase 2 ships."],"exampleFix":"// before\nfor ch in channels.values() {\n    ch.listen(tx.clone()).await?; // fails for the ACP channel\n}\n\n// after\nfor ch in channels.values() {\n    if ch.name() == \"acp\" {\n        continue; // ACP is client-driven; no listener task\n    }\n    ch.listen(tx.clone()).await?;\n}","handlingStrategy":"validation","validationCode":"fn needs_listener(ch: &dyn Channel) -> bool {\n    // ACP channels are client-driven; they refuse listen() by design.\n    ch.name() != \"acp\"\n}","typeGuard":"fn is_client_driven(ch: &dyn Channel) -> bool {\n    ch.name() == \"acp\" && !ch.supports_free_form_ask()\n}","tryCatchPattern":null,"preventionTips":["Query supports_free_form_ask() and name() before wiring listener tasks","Treat channels as capability sets, not uniform bidirectional interfaces","Register ACP channels only in the tool-facing map, never in the listener supervisor"],"tags":["rust","acp","channel-trait","unsupported-operation","capability-check"],"backgroundTag":"unsupported-operation","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}