{"record":{"id":"7eca97917e13fac3","repo":"zeroclaw-labs/zeroclaw","slug":"valid-api-version-header","errorCode":null,"errorMessage":"valid api version header","messagePattern":"valid api version header","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-tools/src/linkedin_client.rs","lineNumber":176,"sourceCode":"\n    fn client() -> reqwest::Client {\n        zeroclaw_config::schema::build_runtime_proxy_client_with_timeouts(\n            \"tool.linkedin\",\n            LINKEDIN_REQUEST_TIMEOUT_SECS,\n            LINKEDIN_CONNECT_TIMEOUT_SECS,\n        )\n    }\n\n    fn api_headers(&self, token: &str) -> HeaderMap {\n        let mut headers = HeaderMap::new();\n        let bearer = format!(\"Bearer {}\", token);\n        headers.insert(\n            reqwest::header::AUTHORIZATION,\n            HeaderValue::from_str(&bearer).expect(\"valid bearer token header\"),\n        );\n        headers.insert(\n            \"LinkedIn-Version\",\n            HeaderValue::from_str(&self.api_version).expect(\"valid api version header\"),\n        );\n        headers.insert(\n            \"X-Restli-Protocol-Version\",\n            HeaderValue::from_static(\"2.0.0\"),\n        );\n        headers\n    }\n\n    async fn api_request(\n        &self,\n        method: Method,\n        url: &str,\n        token: &str,\n        body: Option<serde_json::Value>,\n    ) -> anyhow::Result<reqwest::Response> {\n        let client = Self::client();\n        let headers = self.api_headers(token);\n","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/linkedin_client.rs#L158-L194","documentation":"The same header-safety invariant applied to the `LinkedIn-Version` header. `api_version` is a free String injected through `LinkedInClient::new`; if it contains spaces, newlines, or non-ASCII bytes, `HeaderValue::from_str` fails and `.expect()` panics before the request is sent.","triggerScenarios":"The linkedin tool is configured with an api_version like `\"2025-01 beta\"`, one with a trailing newline, or any non-ASCII variant; the next API call builds headers and panics.","commonSituations":"Hand-edited tool config; copy-paste of version strings from docs or webpages carrying formatting; config generated from templates with untrimmed fields.","solutions":["Set api_version to the strict `YYYY-MM` form LinkedIn expects (e.g. `2025-01`)","Trim and shape-check the configured version where the tool config is parsed","Reject malformed versions at config load with a validation error instead of panicking at request time"],"exampleFix":"// before\nlet ver = self.api_version.clone(); // \"2025-01\\n\"\nHeaderValue::from_str(&ver).expect(\"valid api version header\"),\n\n// after\nlet ver = self.api_version.trim().to_string();\nHeaderValue::from_str(&ver).expect(\"valid api version header\"),","handlingStrategy":"validation","validationCode":"let ver = config_value.trim();\nassert!(is_valid_linkedin_api_version(ver), \"api_version must be YYYY-MM\");","typeGuard":"fn is_valid_linkedin_api_version(v: &str) -> bool {\n    let b = v.as_bytes();\n    v.len() == 7\n        && b[4] == b'-'\n        && b[..4].iter().all(|c| c.is_ascii_digit())\n        && b[5..].iter().all(|c| c.is_ascii_digit())\n}","tryCatchPattern":null,"preventionTips":["Keep api_version in strict YYYY-MM form sourced from LinkedIn's API docs","Validate tool config strings at load time instead of relying on request-time panics"],"tags":["rust","http-headers","linkedin","panic","api-version"],"backgroundTag":"invalid-http-header-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}