Tencent/WeKnora · error
unmarshal blocks: %w
Error message
unmarshal blocks: %w
What it means
GetBlockChildrenFlat fetches the direct children of a Notion block via GET /v1/blocks/{id}/children. After decoding the paginated envelope it re-unmarshals the raw `results` JSON array into []notionBlock. This error wraps that second decode failure: the API returned a valid pagination object whose results entries do not match the library's notionBlock schema (unknown/renamed fields, wrong types).
Source
Thrown at internal/datasource/connector/notion/client.go:259
for {
path := fmt.Sprintf("/v1/blocks/%s/children", blockID)
if startCursor != "" {
path += "?start_cursor=" + startCursor
}
respBody, err := c.doRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, fmt.Errorf("get block children for %s: %w", blockID, err)
}
var resp paginatedResponse
if err := json.Unmarshal(respBody, &resp); err != nil {
return nil, fmt.Errorf("unmarshal block children response: %w", err)
}
var blocks []notionBlock
if err := json.Unmarshal(resp.Results, &blocks); err != nil {
return nil, fmt.Errorf("unmarshal blocks: %w", err)
}
allBlocks = append(allBlocks, blocks...)
if !resp.HasMore || resp.NextCursor == "" {
break
}
startCursor = resp.NextCursor
}
return allBlocks, nil
}
const maxBlockDepth = 5 // Limit recursion depth — deeper content has diminishing value for knowledge bases
const maxBlocksPerPage = 1000 // Limit total blocks fetched per page to prevent runaway API calls
// GetBlockChildrenAll recursively fetches all blocks under a given block ID,
// building a tree structure with Children populated for blocks with has_children=true.View on GitHub (pinned to 988cbb0330)
Solutions
- Update notionBlock (and its block-type payload structs) to match the current Notion API schema for /v1/blocks/{id}/children results
- Log the raw resp.Results body on decode failure to see the exact JSON that broke decoding
- Pin the Notion-Version header to a version your structs are written for
- If running against a mock/test server, fix the fixture to produce a valid results array of block objects
Example fix
// before (opaque failure, schema drift invisible)
if err := json.Unmarshal(resp.Results, &blocks); err != nil {
return nil, fmt.Errorf("unmarshal blocks: %w", err)
}
// after (diagnose drift, tolerate unknown block types)
if err := json.Unmarshal(resp.Results, &blocks); err != nil {
logger.Warnf(ctx, "[Notion] block children decode failed for %s: %v; raw: %s", blockID, err, string(resp.Results))
return nil, fmt.Errorf("unmarshal blocks: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
var probe struct{ Results []json.RawMessage `json:"results"` }
if err := json.Unmarshal(body, &probe); err != nil || len(probe.Results) == 0 {
return fmt.Errorf("no decodable block results")
}
var b notionBlock
if len(probe.Results) > 0 {
if err := json.Unmarshal(probe.Results[0], &b); err != nil {
return fmt.Errorf("block schema mismatch: %w", err)
}
} Type guard
func isNotionBlockArray(raw json.RawMessage) bool {
var arr []map[string]any
return json.Unmarshal(raw, &arr) == nil &&
len(arr) > 0 &&
_, ok := arr[0]["object"].(string) && arr[0]["object"] == "block"
} Try / catch
blocks, err := client.GetBlockChildrenFlat(ctx, blockID)
if err != nil {
var uerr *json.UnmarshalTypeError
if errors.As(err, &uerr) && strings.Contains(err.Error(), "unmarshal blocks") {
// schema drift: log raw payload, degrade to empty children
logger.Warnf(ctx, "notion block schema drift for %s: %v", blockID, err)
return nil, nil
}
return err
} Prevention
- Pin the Notion-Version header and update notionBlock structs whenever you bump it
- Keep unknown block-type payloads in a json.RawMessage field so new types decode without error
- Test against live-recorded fixtures regenerated from the real API, not hand-written JSON
- Add a decode-failure log of the raw results payload to speed up diagnosing drift
When it happens
Trigger: Notion API returns block objects whose shape does not fit notionBlock — e.g. a Notion API version change introduces new block type payloads with differently-typed fields, a proxy/mock returns results as a non-array (object or null-shaped) JSON, or blocks contain fields typed differently than the struct expects (e.g. rich_text vs text).
Common situations: Developers pinning an outdated notionBlock struct after Notion ships a new block type; testing against recorded fixtures or mocks with hand-written results; Notion deprecating/renaming response fields (e.g. older `text` arrays replaced by `rich_text`); a corporate proxy stripping or rewriting response bodies.
Related errors
- cube api: create sandbox: empty sandboxID
- rate limited: %s
- server error %d: %s
- unexpected status %d: %s
- %w: %v
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/e9aa4c245dc0304f.
Report an issue: GitHub.