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

AcpChannel does not support reactions

Error message

AcpChannel does not support reactions

What it means

ACP renders agent output as streamed message chunks and has no per-message reaction primitive, so add_reaction always returns an error. ZeroClaw refuses to silently no-op (the trait default would report success), so the reaction tool's caller learns the truth that the reaction cannot be delivered on this channel.

Source

Thrown at crates/zeroclaw-channels/src/acp_channel.rs:327

            "AcpChannel.listen is not supported (free-form ask_user awaits ACP elicitation Phase 2)"
        )
    }

    fn supports_free_form_ask(&self) -> bool {
        false
    }

    async fn add_reaction(
        &self,
        _channel_id: &str,
        _message_id: &str,
        _emoji: &str,
    ) -> anyhow::Result<()> {
        // ACP renders agent output as message chunks — there's no per-message
        // reaction primitive in the protocol, so silently no-oping (the trait
        // default) would falsely report success to the agent. Surface as Err
        // so the `reaction` tool's caller sees the truth.
        anyhow::bail!("AcpChannel does not support reactions")
    }

    async fn remove_reaction(
        &self,
        _channel_id: &str,
        _message_id: &str,
        _emoji: &str,
    ) -> anyhow::Result<()> {
        anyhow::bail!("AcpChannel does not support reactions")
    }

    async fn request_choice(
        &self,
        question: &str,
        choices: &[String],
        timeout: Duration,
    ) -> anyhow::Result<Option<String>> {
        if choices.is_empty() {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Gate the reaction tool by channel capability — skip or short-circuit it when the channel is ACP.
  2. Replace the reaction with a text acknowledgement sent via Channel::send, which ACP supports as an agent_message_chunk.
  3. Catch the error and tell the agent reactions are unsupported so it picks a different affordance.

Example fix

// before
ch.add_reaction(channel_id, message_id, "+1").await?;

// after
if ch.name() == "acp" {
    ch.send(&SendMessage::new("ack: done", recipient)).await?;
} else {
    ch.add_reaction(channel_id, message_id, "+1").await?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn supports_reactions(ch: &dyn Channel) -> bool {
    // ACP has no per-message reaction primitive; add_reaction fails by design.
    ch.name() != "acp"
}

Type guard

fn is_reaction_capable(ch: &dyn Channel) -> bool {
    ch.name() != "acp"
}

Prevention

When it happens

Trigger: The agent invokes the reaction tool (attach an emoji to a message) while the active channel is the ACP back-channel — i.e., during a session driven from an ACP editor/orchestrator client.

Common situations: Prompts and skills written for chat channels (Slack-style emoji acknowledgements) reused verbatim in ACP sessions; agent loops that add a reaction after every tool result.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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