zeroclaw-labs/zeroclaw · error · anyhow::Error
Push API failed ({status}): {err}
Error message
Push API failed ({status}): {err} What it means
LINE channel: send_push POSTs to /v2/bot/message/push and got non-2xx. Push bypasses reply tokens but is restricted by plan and quota on the LINE Messaging API, so the frequent causes are plan-level denial or quota exhaustion rather than payload shape. Status and error body are included.
Source
Thrown at crates/zeroclaw-channels/src/line.rs:1084
.collect();
for batch in messages.chunks(5) {
let body = serde_json::json!({
"to": to,
"messages": batch,
});
let resp = self
.client
.post(&url)
.bearer_auth(&self.channel_access_token)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let err = resp.text().await.unwrap_or_default();
anyhow::bail!("Push API failed ({status}): {err}");
}
}
Ok(())
}
pub(crate) async fn listen_with_listener(
&self,
listener: tokio::net::TcpListener,
bot_user_id: String,
tx: tokio::sync::mpsc::Sender<ChannelMessage>,
) -> anyhow::Result<()> {
let state = Arc::new(LineState {
tx,
channel_secret: self.channel_secret.clone(),
bot_user_id,
dm_policy: self.dm_policy.clone(),
group_policy: self.group_policy.clone(),
alias: self.alias.clone(),View on GitHub (pinned to 88bb9c8533)
Solutions
- Branch on status: 403 -> enable/upgrade the messaging plan in the LINE console; 429 -> respect quota headers and back off or resume next cycle; 400 -> verify the userId format; 401 -> refresh the channel access token.
- Monitor remaining quota and alert before exhaustion.
- Where possible prefer replies over pushes to conserve quota.
Defensive patterns
Strategy: retry
Validate before calling
// Validate target shape before pushing
fn is_line_user_id(s: &str) -> bool { s.starts_with('U') && s.len() == 33 }
anyhow::ensure!(is_line_user_id(&user_id), "invalid LINE userId for push: {user_id}"); Type guard
fn is_push_quota_or_plan_error(err: &anyhow::Error) -> bool {
let s = err.to_string();
s.contains("Push API failed") && (s.contains("403") || s.contains("429"))
} Try / catch
match line.send_push(&user_id, &msg).await {
Err(e) if e.to_string().contains("Push API failed (429") => schedule_retry_after_quota_reset(msg),
Err(e) if e.to_string().contains("Push API failed (403") => alert("plan does not allow push; enable in console"),
other => other,
} Prevention
- Monitor LINE message quota and alert before month-end exhaustion.
- Verify push is enabled on the channel plan before building push-dependent flows.
- Prefer reply tokens when available; reserve push for initiated/proactive messages.
When it happens
Trigger: 403 when the channel's plan does not permit push messages; 429 when the monthly/daily message quota is exhausted; 400 with an invalid userId (must be a LINE user ID starting with 'U', not a room/group id misused); 401 with an invalid channel access token.
Common situations: Trials or downgraded plans losing push access mid-deployment; volume spikes exhausting quota near month-end; using a reply userId where a different target type is required.
Related errors
- Reply API failed ({status}): {err}
- audio download failed ({status}) for message {message_id}
- audio exceeds {} byte limit for message {message_id}
- Cannot persist empty LINE userId
- Missing [channels.line.{}] section
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2519a8cc0041701d.
Report an issue: GitHub.