{"record":{"id":"2519a8cc0041701d","repo":"zeroclaw-labs/zeroclaw","slug":"push-api-failed-status-err","errorCode":null,"errorMessage":"Push API failed ({status}): {err}","messagePattern":"Push API failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/line.rs","lineNumber":1084,"sourceCode":"            .collect();\n\n        for batch in messages.chunks(5) {\n            let body = serde_json::json!({\n                \"to\": to,\n                \"messages\": batch,\n            });\n            let resp = self\n                .client\n                .post(&url)\n                .bearer_auth(&self.channel_access_token)\n                .json(&body)\n                .send()\n                .await?;\n\n            if !resp.status().is_success() {\n                let status = resp.status();\n                let err = resp.text().await.unwrap_or_default();\n                anyhow::bail!(\"Push API failed ({status}): {err}\");\n            }\n        }\n        Ok(())\n    }\n\n    pub(crate) async fn listen_with_listener(\n        &self,\n        listener: tokio::net::TcpListener,\n        bot_user_id: String,\n        tx: tokio::sync::mpsc::Sender<ChannelMessage>,\n    ) -> anyhow::Result<()> {\n        let state = Arc::new(LineState {\n            tx,\n            channel_secret: self.channel_secret.clone(),\n            bot_user_id,\n            dm_policy: self.dm_policy.clone(),\n            group_policy: self.group_policy.clone(),\n            alias: self.alias.clone(),","sourceCodeStart":1066,"sourceCodeEnd":1102,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/line.rs#L1066-L1102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate target shape before pushing\nfn is_line_user_id(s: &str) -> bool { s.starts_with('U') && s.len() == 33 }\nanyhow::ensure!(is_line_user_id(&user_id), \"invalid LINE userId for push: {user_id}\");","typeGuard":"fn is_push_quota_or_plan_error(err: &anyhow::Error) -> bool {\n    let s = err.to_string();\n    s.contains(\"Push API failed\") && (s.contains(\"403\") || s.contains(\"429\"))\n}","tryCatchPattern":"match line.send_push(&user_id, &msg).await {\n    Err(e) if e.to_string().contains(\"Push API failed (429\") => schedule_retry_after_quota_reset(msg),\n    Err(e) if e.to_string().contains(\"Push API failed (403\") => alert(\"plan does not allow push; enable in console\"),\n    other => other,\n}","preventionTips":["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."],"tags":["line","push","quota","plan-restriction","messaging-api"],"backgroundTag":"api-permission-denied","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}