{"record":{"id":"2ebf4dd006f90dd4","repo":"zeroclaw-labs/zeroclaw","slug":"linkedin-add-comment-failed","errorCode":null,"errorMessage":"LinkedIn add_comment failed ({}): {}","messagePattern":"LinkedIn add_comment failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/linkedin_client.rs","lineNumber":389,"sourceCode":"            \"{}/rest/socialActions/{}/comments\",\n            LINKEDIN_API_BASE, post_id\n        );\n\n        let body = json!({\n            \"actor\": actor_urn,\n            \"message\": {\n                \"text\": text\n            }\n        });\n\n        let response = self\n            .api_request(Method::POST, &url, &creds.access_token, Some(body))\n            .await?;\n\n        let status = response.status();\n        if !status.is_success() {\n            let body_text = response.text().await.unwrap_or_default();\n            anyhow::bail!(\"LinkedIn add_comment failed ({}): {}\", status, body_text);\n        }\n\n        let json: serde_json::Value = response\n            .json()\n            .await\n            .context(\"Failed to parse add_comment response\")?;\n\n        let comment_id = json\n            .get(\"id\")\n            .and_then(|v| v.as_str())\n            .unwrap_or_default()\n            .to_string();\n\n        Ok(comment_id)\n    }\n\n    pub async fn add_reaction(&self, post_id: &str, reaction_type: &str) -> anyhow::Result<()> {\n        let creds = self.get_credentials().await?;","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/linkedin_client.rs#L371-L407","documentation":"Thrown when POST of a comment to a LinkedIn post returns a non-2xx status; the raw body is embedded. Commenting requires the same social scope as posting plus a valid post URN. After this check, the response is parsed as JSON, so malformed URNs usually fail here with a 400/404 first.","triggerScenarios":"Commenting on a post URN that is malformed or deleted (400/404); token lacking w_member_social (403); expired token (401); commenting on another member's post where comments are restricted.","commonSituations":"Using the post id returned by create_post after that post was deleted; URN strings built by concatenation with wrong prefixes (urn:li:comment vs urn:li:share); stale tokens in long-lived integrations.","solutions":["Verify the post URN format: urn:li:share:{id} (or urn:li:ugcPost:{id}) and that the post still exists.","For 403, ensure w_member_social scope; for 401, refresh the token.","For 404, re-list posts to get a current URN before commenting."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Cheap pre-check: the post must still exist before commenting\nlet posts = client.list_posts(&token).await?;\nlet still_there = posts.iter().any(|p| p.urn() == target_urn);\nif !still_there { anyhow::bail!(\"post {target_urn} gone; skip comment\"); }","typeGuard":null,"tryCatchPattern":"match client.add_comment(&token, &target_urn, text).await {\n    Ok(c) => Ok(c),\n    Err(e) if e.to_string().contains(\"(404\") => { /* post deleted; drop quietly */ Ok(skip()) }\n    Err(e) => Err(e),\n}","preventionTips":["Comment only on URNs returned from a fresh list_posts/create_post call in the same run.","Normalize URN prefixes (urn:li:share vs urn:li:ugcPost) consistently in your persistence layer.","Re-check existence before bulk comment sweeps."],"tags":["linkedin","http-status","comments"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}