zeroclaw-labs/zeroclaw · error
LinkedIn list_posts failed ({}): {}
Error message
LinkedIn list_posts failed ({}): {} What it means
Thrown when GET on the posts listing endpoint returns a non-2xx status, with the raw body included. Listing posts requires read access to the member's shares; the common failure modes are scope/permission (403) and expired token (401). On success the same response is parsed as JSON, so error bodies that are HTML (auth walls) also surface through this path's status check first.
Source
Thrown at crates/zeroclaw-tools/src/linkedin_client.rs:324
Ok(post_urn)
}
pub async fn list_posts(&self, count: usize) -> anyhow::Result<Vec<PostSummary>> {
let creds = self.get_credentials().await?;
let author_urn = format!("urn:li:person:{}", creds.person_id);
let url = format!(
"{}/rest/posts?author={}&q=author&count={}",
LINKEDIN_API_BASE, author_urn, count
);
let response = self
.api_request(Method::GET, &url, &creds.access_token, None)
.await?;
let status = response.status();
if !status.is_success() {
let body_text = response.text().await.unwrap_or_default();
anyhow::bail!("LinkedIn list_posts failed ({}): {}", status, body_text);
}
let json: serde_json::Value = response
.json()
.await
.context("Failed to parse list_posts response")?;
let elements = json
.get("elements")
.and_then(|e| e.as_array())
.cloned()
.unwrap_or_default();
let posts = elements
.iter()
.map(|el| PostSummary {
id: el
.get("id")View on GitHub (pinned to 88bb9c8533)
Solutions
- Check the status: 401 => refresh/re-auth; 403 => add the required read scope/product to the app and re-consent; 429 => retry later.
- Store the refresh token alongside the access token so api_request can auto-refresh.
- Re-authorize the member if the app's scopes changed since the grant.
Defensive patterns
Strategy: retry
Try / catch
// Distinguish permanent (4xx) from transient (429/5xx) failures
for attempt in 0..3 {
match client.list_posts(&token).await {
Ok(posts) => break Ok(posts),
Err(e) if e.to_string().contains("(429") || e.to_string().contains("(50") => {
tokio::time::sleep(backoff(attempt)).await;
}
Err(e) => break Err(e),
}?
} Prevention
- Schedule listing with modest intervals to stay clear of LinkedIn rate limits.
- Refresh tokens on a timer rather than waiting for a 401 mid-call.
- Log the response body once per failure class — LinkedIn's JSON pinpoints scope problems.
When it happens
Trigger: Calling the linkedin list_posts action with an expired access token and no refresh token stored; token granted only posting scope; requesting another member's posts; hitting LinkedIn rate limits (429).
Common situations: Long-running services holding tokens beyond the 60-day expiry; developer apps not approved for the requested products; mixing tokens between the developer app that created them.
Related errors
- LinkedIn create_post failed ({}): {}
- LinkedIn add_comment failed ({}): {}
- LinkedIn add_reaction failed ({}): {}
- LinkedIn delete_post failed ({}): {}
- LinkedIn get_engagement failed ({}): {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/6dcb98626d40ab3e.
Report an issue: GitHub.