zeroclaw-labs/zeroclaw · error · anyhow::Error
Notion update_page failed ({status}): {truncated}
Error message
Notion update_page failed ({status}): {truncated} What it means
The Notion tool's update_page action sent PATCH https://api.notion.com/v1/pages/{page_id} and got a non-2xx response. The wrapped status and body excerpt identify the rejection cause, almost always a property payload that does not match the page's database schema or an archived page.
Source
Thrown at crates/zeroclaw-tools/src/notion_tool.rs:152
page_id: &str,
properties: &serde_json::Value,
) -> anyhow::Result<serde_json::Value> {
let url = format!("{NOTION_API_BASE}/pages/{page_id}");
let body = json!({ "properties": properties });
let resp = self
.http
.patch(&url)
.headers(self.headers()?)
.json(&body)
.timeout(std::time::Duration::from_secs(NOTION_REQUEST_TIMEOUT_SECS))
.send()
.await?;
let status = resp.status();
if !status.is_success() {
let text = resp.text().await.unwrap_or_default();
let truncated =
crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS);
anyhow::bail!("Notion update_page failed ({status}): {truncated}");
}
resp.json().await.map_err(Into::into)
}
/// Search the Notion workspace by query string.
async fn search(&self, query: &str) -> anyhow::Result<serde_json::Value> {
let url = format!("{NOTION_API_BASE}/search");
let body = json!({ "query": query });
let resp = self
.http
.post(&url)
.headers(self.headers()?)
.json(&body)
.timeout(std::time::Duration::from_secs(NOTION_REQUEST_TIMEOUT_SECS))
.send()
.await?;
let status = resp.status();
if !status.is_success() {View on GitHub (pinned to 88bb9c8533)
Solutions
- Match the body excerpt to the failing property name and fix its value type (select needs {select:{name}}, numbers need {number:n})
- If the page was archived, restore it in Notion or drop it from the update queue
- Re-read the page to refresh property names/types before building the update payload
- For 404, fix sharing; for 401, rotate the token
Example fix
// before ('Status' is a select property)
{"action":"update_page","page_id":"<id>","properties":{"Status":{"rich_text":[{"text":{"content":"Done"}}]}}}
// after
{"action":"update_page","page_id":"<id>","properties":{"Status":{"select":{"name":"Done"}}}} Defensive patterns
Strategy: validation
Validate before calling
// Before updating, confirm the page is live and capture property types
let page = notion.execute(json!({"action":"read_page","page_id":id})).await?;
if page.get("archived") == Some(&json!(true)) { /* skip */ } Type guard
fn is_editable_page(v: &serde_json::Value) -> bool {
v.get("archived").and_then(|a| a.as_bool()) == Some(false)
} Try / catch
Err(e) if e.to_string().starts_with("Notion update_page failed (400)") => {
// parse body excerpt for property name, re-read schema, coerce type, retry once
} Prevention
- Map each outgoing field through the property type read from the page
- Drop archived pages from update queues when Notion signals deletion
- Keep payloads minimal: send only properties that actually changed
When it happens
Trigger: Sending a value with the wrong property type (e.g. rich_text for a select or number property), referencing a property that no longer exists, updating an archived/trashed page (400), page not shared with the integration (404), or invalid token (401).
Common situations: Sync jobs that push field updates into Notion break after schema drift: someone retyped a field from text to select, so the old payload shape now fails. Archived pages still referenced by stored ids are another frequent source.
Related errors
- Notion read_page failed ({status}): {truncated}
- Notion create_page failed ({status}): {truncated}
- Notion query_database failed ({status}): {truncated}
- Notion search failed ({status}): {truncated}
- Failed to initiate call: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2bc601a9341eff19.
Report an issue: GitHub.