{"record":{"id":"46d861e6d895ba2c","repo":"zeroclaw-labs/zeroclaw","slug":"linkedin-get-profile-failed","errorCode":null,"errorMessage":"LinkedIn get_profile failed ({}): {}","messagePattern":"LinkedIn get_profile failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/linkedin_client.rs","lineNumber":501,"sourceCode":"        Ok(EngagementSummary {\n            likes,\n            comments,\n            shares,\n        })\n    }\n\n    pub async fn get_profile(&self) -> anyhow::Result<ProfileInfo> {\n        let creds = self.get_credentials().await?;\n        let url = format!(\"{}/rest/me\", LINKEDIN_API_BASE);\n\n        let response = self\n            .api_request(Method::GET, &url, &creds.access_token, None)\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 get_profile failed ({}): {}\", status, body_text);\n        }\n\n        let json: serde_json::Value = response\n            .json()\n            .await\n            .context(\"Failed to parse get_profile response\")?;\n\n        let id = json\n            .get(\"id\")\n            .and_then(|v| v.as_str())\n            .unwrap_or_default()\n            .to_string();\n\n        let first_name = json\n            .get(\"localizedFirstName\")\n            .and_then(|v| v.as_str())\n            .unwrap_or_default();\n","sourceCodeStart":483,"sourceCodeEnd":519,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/linkedin_client.rs#L483-L519","documentation":"Thrown when GET {LINKEDIN_API_BASE}/rest/me returns non-2xx, body included. This is usually the first call made with a newly granted token (used to resolve the member id), so failures here are typically token- or scope-level rather than data-level. The parsed profile fields default to empty strings rather than erroring, so this status check is the main failure point.","triggerScenarios":"Expired access token with no refresh token available (401); token granted without the profile/openid scope (403); app missing the Sign In with LinkedIn product; calling immediately after grant before propagation.","commonSituations":"OAuth integrations where only w_member_social was requested; tokens past their lifetime in long-running daemons; developer apps that never added the sign-in product; environment with the wrong client_id/secret pair so the token is invalid.","solutions":["For 401, refresh the access token (ensure a refresh token was stored) or re-run the OAuth flow.","For 403, add the profile/sign-in product and openid profile scope to the app, then re-authorize.","Verify client_id/client_secret/access_token all come from the same LinkedIn app.","Retry once after a short delay if the token was just granted."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.get_profile().await {\n    Ok(p) => Ok(p),\n    Err(e) if e.to_string().contains(\"(401\") => {\n        // force token refresh, then one retry; if refresh token absent, start OAuth flow\n        Err(e).context(\"LinkedIn session expired; re-auth required\")\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Call get_profile once right after OAuth to validate scopes/tokens before doing real work.","Store refresh_token at grant time so silent renewal is possible.","Keep client_id/secret/access/refresh tokens from the same app in one credential record."],"tags":["linkedin","http-status","profile","oauth"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}