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

  1. Update notionBlock (and its block-type payload structs) to match the current Notion API schema for /v1/blocks/{id}/children results
  2. Log the raw resp.Results body on decode failure to see the exact JSON that broke decoding
  3. Pin the Notion-Version header to a version your structs are written for
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/e9aa4c245dc0304f. Report an issue: GitHub.