zeroclaw-labs/zeroclaw · warning · anyhow::Error
Slack reactions.remove failed: {err}
Error message
Slack reactions.remove failed: {err} What it means
Thrown when the Slack Web API `reactions.remove` call returns `ok: false` with an error code other than `no_reaction`. The library already treats `no_reaction` as success (the emoji is already gone), so any other Slack error code — message_not_found, too_old, cant_remove_reaction, not_authed — propagates with the raw code embedded in the message.
Source
Thrown at crates/zeroclaw-channels/src/slack.rs:5563
.send()
.await?;
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
if !status.is_success() {
let sanitized = zeroclaw_providers::sanitize_api_error(&text);
anyhow::bail!("Slack reactions.remove failed ({status}): {sanitized}");
}
let parsed: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
if parsed.get("ok") == Some(&serde_json::Value::Bool(false)) {
let err = parsed
.get("error")
.and_then(|e| e.as_str())
.unwrap_or("unknown");
if err != "no_reaction" {
anyhow::bail!("Slack reactions.remove failed: {err}");
}
}
Ok(())
}
async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {
// Cache the bot user id on the struct so `self_handle` (sync,
// called by the orchestrator's self-loop guard on every inbound)
// resolves without an additional `auth.test` round-trip.
self.cache_bot_user_id().await;
let bot_user_id = self.get_bot_user_id().await.unwrap_or_default();
let scoped_channels = self.scoped_channel_ids();
if self.configured_app_token().is_some() {
::zeroclaw_log::record!(
INFO,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note),
"channel listening in Socket Mode"View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the Slack error code embedded after 'failed:' — message_not_found/to_old mean the target is gone and can be treated as benign.
- Verify the bot token includes `reactions:write` and reinstall the Slack app if scopes changed.
- Confirm the `channel` + `timestamp` pair passed to reactions.remove comes from the same event payload that reported the reaction.
- On cleanup/dedup paths, catch the error and downgrade to a warning, mirroring the library's own no_reaction tolerance.
Example fix
// before
channel.remove_reaction(&channel_id, &ts, &emoji).await?;
// after
if let Err(e) = channel.remove_reaction(&channel_id, &ts, &emoji).await {
let msg = e.to_string();
if msg.contains("no_reaction") || msg.contains("message_not_found") || msg.contains("too_old") {
tracing::warn!("reaction already gone: {e}");
} else {
return Err(e);
}
} Defensive patterns
Strategy: try-catch
Type guard
fn slack_reaction_error_code(err: &anyhow::Error) -> Option<&str> {
err.to_string()
.strip_prefix("Slack reactions.remove failed: ")
} Try / catch
if let Err(e) = channel.remove_reaction(ch, ts, emoji).await {
match slack_reaction_error_code(&e) {
Some("no_reaction") | Some("message_not_found") | Some("too_old") => {
tracing::warn!("reaction target gone: {e}");
}
_ => return Err(e),
}
} Prevention
- Cache reaction ts/channel from the event payload and remove promptly, before messages can be deleted or age out.
- Keep the reactions:write scope in the app manifest and re-verify after token rotation.
- Treat reaction removal as best-effort: warn instead of propagating on cleanup paths.
When it happens
Trigger: Calling reactions.remove with a `timestamp` for a deleted message or one older than Slack's reaction window (message_not_found, too_old); a bot token missing the reactions:write scope (cant_remove_reaction, not_allowed); an invalid or revoked token (not_authed); a `channel` ID the bot cannot see (channel_not_found).
Common situations: Bot token regenerated and the app not reinstalled with the new scopes; retrying reaction cleanup after the target message was deleted; races where a parallel process removed the reaction; classic-bot vs workspace-app permission mismatches.
Related errors
- assistant.threads.setStatus failed: {}
- AcpChannel does not support reactions
- Discord add reaction failed ({status}): {err}
- Discord remove reaction failed ({status}): {err}
- bot info failed: code={code}, body={body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c52718f4bb1d00ea.
Report an issue: GitHub.