sipeed/picoclaw · warning
channel %s does not support reactions
Error message
channel %s does not support reactions
What it means
The resolved channel exists but does not implement channels.ReactionCapable (pkg/channels/interfaces.go:42), the interface with ReactToMessage(ctx, chatID, messageID). The reaction tool type-asserts the channel to this interface; only some channel types implement it. This is a permanent capability mismatch — retrying against the same channel will always fail.
Source
Thrown at pkg/agent/agent_init.go:233
pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer pubCancel()
return msgBus.PublishOutbound(pubCtx, outboundMessage)
})
agent.Tools.Register(messageTool)
}
if cfg.Tools.IsToolEnabled("reaction") {
reactionTool := tools.NewReactionTool()
reactionTool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error {
if al.channelManager == nil {
return fmt.Errorf("channel manager not configured")
}
ch, ok := al.channelManager.GetChannel(channel)
if !ok {
return fmt.Errorf("channel %s not found", channel)
}
rc, ok := ch.(channels.ReactionCapable)
if !ok {
return fmt.Errorf("channel %s does not support reactions", channel)
}
_, err := rc.ReactToMessage(ctx, chatID, messageID)
return err
})
agent.Tools.Register(reactionTool)
}
// Send file tool (outbound media via MediaStore — store injected later by SetMediaStore)
if cfg.Tools.IsToolEnabled("send_file") {
sendFileTool := tools.NewSendFileTool(
agent.Workspace,
cfg.Agents.Defaults.RestrictToWorkspace,
cfg.Agents.Defaults.GetMaxMediaSize(),
nil,
allowReadPaths,
)
agent.Tools.Register(sendFileTool)
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Treat reactions as best-effort on this channel and instruct the model (system prompt) to only use reactions on channels that support them
- If it is your own channel, implement ReactToMessage returning an idempotent undo func (see interface contract)
- Disable the reaction tool if none of your channels support it
- Pick a reaction-capable channel (e.g. Telegram) for flows that rely on the 👀 acknowledgement
Example fix
// before — custom channel without reactions
type MyChannel struct{ /* ... */ }
// after — opt in to reactions
var _ channels.ReactionCapable = (*MyChannel)(nil)
func (c *MyChannel) ReactToMessage(ctx context.Context, chatID, messageID string) (func(), error) {
if err := c.api.AddReaction(ctx, chatID, messageID, "👀"); err != nil {
return nil, err
}
return func() { _ = c.api.RemoveReaction(ctx, chatID, messageID, "👀") }, nil // must be idempotent
} Defensive patterns
Strategy: type-guard
Validate before calling
ch, ok := cm.GetChannel(name)
if !ok {
return fmt.Errorf("channel %q not registered", name)
}
if _, capable := ch.(channels.ReactionCapable); !capable {
// skip the reaction flow; this channel can never react
return nil
} Type guard
func supportsReactions(ch channels.Channel) bool {
_, ok := ch.(channels.ReactionCapable)
return ok
} Prevention
- Add compile-time assertions on custom channels: var _ channels.ReactionCapable = (*MyChannel)(nil)
- Check the capability before enabling reaction affordances in UI/flows, not after the tool call fails
- Remember the undo func returned by ReactToMessage must be idempotent — a broken undo breaks later mounts
When it happens
Trigger: Reaction tool invoked against a channel type that lacks ReactToMessage (console/IRC-style channels, test stubs, most custom channels), or a custom channel implementation that simply does not define the method.
Common situations: Writing a custom channel and assuming reaction support comes for free; enabling the reaction tool globally while some configured channels cannot react; the model choosing a non-reaction channel by mistake.
Related errors
- channel manager not configured
- channel %s not found
- failed to load MCP servers: %w
- tool discovery is enabled but neither 'use_bm25' nor 'use_re
- no default agent for heartbeat
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/7aed9b550505413b.
Report an issue: GitHub.