{"record":{"id":"99127d1c5724397f","repo":"zeroclaw-labs/zeroclaw","slug":"notion-search-failed-status-truncated","errorCode":null,"errorMessage":"Notion search failed ({status}): {truncated}","messagePattern":"Notion search failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/notion_tool.rs","lineNumber":174,"sourceCode":"\n    /// Search the Notion workspace by query string.\n    async fn search(&self, query: &str) -> anyhow::Result<serde_json::Value> {\n        let url = format!(\"{NOTION_API_BASE}/search\");\n        let body = json!({ \"query\": query });\n        let resp = self\n            .http\n            .post(&url)\n            .headers(self.headers()?)\n            .json(&body)\n            .timeout(std::time::Duration::from_secs(NOTION_REQUEST_TIMEOUT_SECS))\n            .send()\n            .await?;\n        let status = resp.status();\n        if !status.is_success() {\n            let text = resp.text().await.unwrap_or_default();\n            let truncated =\n                crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS);\n            anyhow::bail!(\"Notion search failed ({status}): {truncated}\");\n        }\n        resp.json().await.map_err(Into::into)\n    }\n}\n\n#[async_trait]\nimpl Tool for NotionTool {\n    fn name(&self) -> &str {\n        \"notion\"\n    }\n\n    fn description(&self) -> &str {\n        \"Interact with Notion: query databases, read/create/update pages, and search the workspace.\"\n    }\n\n    fn parameters_schema(&self) -> serde_json::Value {\n        json!({\n            \"type\": \"object\",","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/notion_tool.rs#L156-L192","documentation":"The Notion tool's search action sent POST https://api.notion.com/v1/search and received a non-2xx status; the message carries the status and a truncated error body. Because search only touches content already shared with the integration, failures here are usually authentication, rate limiting, or malformed query payloads rather than per-object access.","triggerScenarios":"Invalid or revoked integration token (401), token without workspace access (403), a filter/sort payload with an invalid shape or unknown property (400), or hammering the endpoint past ~3 requests/second (429 rate_limited).","commonSituations":"Agents that call search before every page operation quickly trip 429. Token rotation without updating config produces sudden 401s on the very first call of a session.","solutions":["For 429, throttle and retry with backoff; cache search results instead of searching per operation","For 401/403, regenerate the integration secret and re-share the workspace content","For 400, simplify the payload: drop filter/sort and send only the query string","Remember search only sees pages/databases explicitly shared with the integration; empty results with 200 are a sharing issue, not this error"],"exampleFix":"// before (tight loop)\nfor page in candidates { tool.execute({\"action\":\"search\",\"query\":page}) }\n// after (search once, then act)\nlet found = tool.execute({\"action\":\"search\",\"query\":\"meeting notes\"}).await?;\nfor page in found.results { tool.execute({\"action\":\"read_page\",\"page_id\":page.id}).await? }","handlingStrategy":"retry","validationCode":"// Throttle searches yourself: token bucket at ~2/s leaves headroom\nlet mut bucket = governor_default();\nfor q in queries { bucket.wait().await; notion.execute(json!({\"action\":\"search\",\"query\":q})).await?; }","typeGuard":null,"tryCatchPattern":"Err(e) if e.to_string().starts_with(\"Notion search failed (429)\") => {\n    tokio::time::sleep(Duration::from_secs(2u64.pow(attempt).min(32))).await; // retry\n}","preventionTips":["Cache search results for the session instead of searching per operation","Batch discovery once at startup rather than continuously","Monitor for 401 flips after token rotation so the first failure alerts, not the hundredth"],"tags":["notion","http","api","search","rate-limit","rust"],"backgroundTag":"notion-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}