{"record":{"id":"13723827ad2313a7","repo":"Tencent/WeKnora","slug":"unexpected-status-d-s","errorCode":null,"errorMessage":"unexpected status %d: %s","messagePattern":"unexpected status (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/connector/notion/client.go","lineNumber":144,"sourceCode":"\t\t\tlastErr = fmt.Errorf(\"rate limited: %s\", string(respBody))\n\t\t\tif attempt < maxRetries {\n\t\t\t\tif sErr := sleepWithContext(ctx, wait); sErr != nil {\n\t\t\t\t\treturn nil, sErr\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\tcase resp.StatusCode >= 500:\n\t\t\tlastErr = fmt.Errorf(\"server error %d: %s\", resp.StatusCode, string(respBody))\n\t\t\tif attempt < maxRetries {\n\t\t\t\tif sErr := sleepWithContext(ctx, time.Duration(1<<attempt)*time.Second); sErr != nil {\n\t\t\t\t\treturn nil, sErr\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected status %d: %s\", resp.StatusCode, string(respBody))\n\t\t}\n\t}\n\n\tif lastErr != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %v\", datasource.ErrFetchFailed, lastErr)\n\t}\n\treturn nil, datasource.ErrFetchFailed\n}\n\n// Ping verifies the API token is valid by calling GET /v1/users/me.\nfunc (c *notionClient) Ping(ctx context.Context) error {\n\t_, err := c.doRequest(ctx, http.MethodGet, \"/v1/users/me\", nil)\n\treturn err\n}\n\n// SearchPages returns all pages and databases accessible to the integration.\nfunc (c *notionClient) SearchPages(ctx context.Context) ([]notionPage, error) {\n\treturn c.paginatePages(ctx, http.MethodPost, \"/v1/search\")","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/connector/notion/client.go#L126-L162","documentation":"The Notion API returned an HTTP status outside all explicitly handled ranges (not 2xx, 401/403, 404, 429, or 5xx). Unlike retryable cases, this returns immediately with no retry. The message includes the status code and raw response body, which usually contains Notion's JSON error object describing the actual problem.","triggerScenarios":"Any doRequest call receiving a status like 400 (validation_error), 409 (conflict_error / rate_limited variant), 410, or 3xx redirects. E.g. GetPage with a malformed page ID → 400; a 409 conflict from a concurrent edit; a 400 validation error on the search/query POST body.","commonSituations":"Malformed Notion IDs passed in (missing dashes usually OK, but truncated IDs → 400 validation_error); 409 conflicts when content changes mid-sync; API version mismatch (Notion-Version header) causing rejected requests; an endpoint contract change (e.g. data_sources endpoints require 2025-09-03+).","solutions":["Read the JSON body in the error message — Notion's code/message fields (e.g. validation_error, conflict_error) identify the real cause.","Validate Notion page/database/block IDs before calling (32-hex or UUID format) — 400 validation_error usually means a bad ID or path.","Check that NotionAPIVersion matches the endpoints being used; data_sources endpoints need API version 2025-09-03 or newer.","For 409 conflict errors, simply retry the request — it's transient.","If a new status keeps appearing, add an explicit case in the switch at client.go:109 to handle it (e.g. map 400 to a typed error)."],"exampleFix":"// before\ndefault:\n    return nil, fmt.Errorf(\"unexpected status %d: %s\", resp.StatusCode, string(respBody))\n// after: surface 400/409 explicitly\ncase resp.StatusCode == http.StatusBadRequest:\n    return nil, fmt.Errorf(\"%w: %s\", datasource.ErrBadRequest, string(respBody))\ncase resp.StatusCode == http.StatusConflict:\n    // retryable: fall through to retry logic\n    lastErr = fmt.Errorf(\"conflict: %s\", string(respBody))","handlingStrategy":"validation","validationCode":"var notionIDRe = regexp.MustCompile(`^[0-9a-fA-F]{32}$|^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)\nfunc validNotionID(id string) bool { return notionIDRe.MatchString(strings.ReplaceAll(id, \"-\", \"\")) || notionIDRe.MatchString(id) }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"unexpected status 4\") {\n    // parse the JSON body from the error for notion's code field\n    var apiErr struct{ Code string `json:\"code\"`; Message string `json:\"message\"` }\n    // log apiErr.Code: validation_error vs conflict_error\n}","preventionTips":["Validate page/database/block ID format before calling the API","Keep NotionAPIVersion pinned and consistent with the endpoints used","Treat 409 as retryable — re-issue the request once","Map frequent statuses to typed errors in the switch for better handling"],"tags":["notion","http-status","api-error","validation"],"backgroundTag":"unexpected-http-status","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}