zeroclaw-labs/zeroclaw · warning · anyhow::Error
Lark add_reaction failed for {message_id}: status={status},
Error message
Lark add_reaction failed for {message_id}: status={status}, body={err_body} What it means
Adding an emoji reaction (POST /im/v1/messages/{message_id}/reactions) returned non-2xx. The message includes the message_id, status, and the raw body text, mapping directly to Lark's error catalog. Reactions are cosmetic acknowledgments, so callers usually treat this as non-fatal; the code!=0 variant (200 with business code) is checked separately right after this branch.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:2702
let mut token = self.get_tenant_access_token().await?;
let mut retried = false;
loop {
let response = self
.post_message_reaction_with_token(message_id, &token, emoji_type)
.await?;
if response.status().as_u16() == 401 && !retried {
self.invalidate_token().await;
token = self.get_tenant_access_token().await?;
retried = true;
continue;
}
if !response.status().is_success() {
let status = response.status();
let err_body = response.text().await.unwrap_or_default();
anyhow::bail!(
"Lark add_reaction failed for {message_id}: status={status}, body={err_body}"
);
}
let payload: serde_json::Value = response.json().await?;
let code = payload.get("code").and_then(|v| v.as_i64()).unwrap_or(-1);
if code != 0 {
let msg = payload
.get("msg")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
.with_outcome(::zeroclaw_log::EventOutcome::Unknown)
.with_attrs(::serde_json::json!({
"code": code,
"message_id": message_id,View on GitHub (pinned to 88bb9c8533)
Solutions
- Read body= in the message and map the Lark code: permission -> grant the reaction scope and republish; invalid emoji -> use a standard emoji_type key.
- If the bot was removed from the chat, skip reacting for that chat.
- Treat as best-effort: log and continue rather than failing the message pipeline.
- Rate-limit reaction calls if 429.
Defensive patterns
Strategy: try-catch
Type guard
fn is_add_reaction_failure(err: &anyhow::Error) -> bool {
err.to_string().contains("Lark add_reaction failed")
} Try / catch
if let Err(e) = lark.add_reaction(&message_id, emoji).await {
if is_add_reaction_failure(&e) {
tracing::debug!(error = %e, "reaction skipped (cosmetic)"); // never fail the turn
} else {
return Err(e);
}
} Prevention
- Treat reactions as best-effort with debug-level logging.
- Pin emoji_type values to Lark's standard emoji key list.
- Rate-limit reaction calls to avoid 429 bursts.
When it happens
Trigger: 404 for a message too old, deleted, or in a chat the bot left; 403 when the app lacks the add-reaction scope; 400 for an emoji_type not in Lark's allowed emoji key set; 429 from reaction bursts.
Common situations: Bot removed from the group before the reaction fires, permission changes dropping the reaction scope, or an emoji_type value that Lark does not recognize (custom vs standard emoji keys).
Related errors
- tenant_access_token request failed: status={status}, body={d
- bot info request failed after token refresh: status={retry_s
- bot info request failed: status={status}, body={body}
- audio download failed: {}
- Failed to initiate call: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c9c58e122a786f73.
Report an issue: GitHub.