{"record":{"id":"88799c232875764a","repo":"zeroclaw-labs/zeroclaw","slug":"notion-query-database-failed-status-truncate","errorCode":null,"errorMessage":"Notion query_database failed ({status}): {truncated}","messagePattern":"Notion query_database failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/notion_tool.rs","lineNumber":77,"sourceCode":"        let url = format!(\"{NOTION_API_BASE}/databases/{database_id}/query\");\n        let mut body = json!({});\n        if let Some(f) = filter {\n            body[\"filter\"] = f.clone();\n        }\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 query_database failed ({status}): {truncated}\");\n        }\n        resp.json().await.map_err(Into::into)\n    }\n\n    /// Read a single Notion page by ID.\n    async fn read_page(&self, page_id: &str) -> anyhow::Result<serde_json::Value> {\n        let url = format!(\"{NOTION_API_BASE}/pages/{page_id}\");\n        let resp = self\n            .http\n            .get(&url)\n            .headers(self.headers()?)\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 =","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/notion_tool.rs#L59-L95","documentation":"The Notion tool's query_database action sent POST https://api.notion.com/v1/databases/{database_id}/query (with a Bearer token and Notion-Version 2022-06-28 header) and Notion answered with a non-2xx HTTP status. The message embeds the status code plus up to 500 characters of Notion's JSON error body, which carries the real reason (unauthorized, object_not_found, validation_error, rate_limited).","triggerScenarios":"Querying a database_id that is malformed, deleted, or not shared with the integration (404 object_not_found); a missing/invalid/expired integration token (401); a token without access to that workspace (403); a filter JSON that does not match the database's property names or types (400 validation_error); or exceeding Notion's ~3 requests/second per integration (429).","commonSituations":"The classic case: the integration was created in Notion but the database was never shared with it via '...' > Connections, so a database that visibly exists returns 404. Others: rotated token not updated in zeroclaw secrets config, filter built against stale property names after a schema change, and scripts looping queries until rate limited.","solutions":["Read the status and body fragment in the message first: 401/403 = token problem, 404 = sharing problem, 400 = filter shape, 429 = rate limit","For 404 object_not_found, open the database in Notion, use '...' > Connections, add your integration, then retry the same call","For 401, regenerate the internal integration secret at notion.so/my-integrations and update the stored api_key","For 400 validation_error, strip the filter down to one property, verify the property name/type by reading the database first, then re-add clauses","For 429, add exponential backoff between queries (roughly 3 requests/second ceiling per integration)"],"exampleFix":"// before (tool args)\n{\"action\":\"query_database\",\"database_id\":\"my-tasks-db\",\"filter\":{\"property\":\"Due\",\"date\":{\"after\":\"2026-01-01\"}}}\n// 404 object_not_found -> share the database with the integration, or use the real id:\n{\"action\":\"query_database\",\"database_id\":\"8a4f9c2e1234567890abcdef12345678\",\"filter\":{\"property\":\"Due\",\"date\":{\"after\":\"2026-01-01\"}}}","handlingStrategy":"try-catch","validationCode":"// Before querying, sanity-check the database id shape (Notion ids are 32 hex chars)\nfn valid_notion_id(id: &str) -> bool {\n    let hex: String = id.chars().filter(|c| c != '-').collect();\n    hex.len() == 32 && hex.chars().all(|c| c.is_ascii_hexdigit())\n}","typeGuard":null,"tryCatchPattern":"match tool.execute(args).await {\n    Ok(out) => { /* ... */ }\n    Err(e) => {\n        let msg = e.to_string();\n        if msg.starts_with(\"Notion query_database failed (429)\") {\n            tokio::time::sleep(backoff.next()).await; /* retry */\n        } else if msg.starts_with(\"Notion query_database failed (404)\") {\n            // sharing problem: surface to operator, do not retry\n        } else {\n            return Err(e);\n        }\n    }\n}","preventionTips":["Share every database/page the integration must see via Notion 'Connections' before first use","Store the integration token in secrets config and rotate it on a schedule","Validate filter payloads against a freshly read database schema instead of hardcoding","Cache query results and rate-limit your own calls to stay under ~3 req/s"],"tags":["notion","http","api","database","query","rust"],"backgroundTag":"notion-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}