{"record":{"id":"62465e302fccada2","repo":"Tencent/WeKnora","slug":"w-v","errorCode":null,"errorMessage":"%w: %v","messagePattern":"%w: %v","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/connector/notion/client.go","lineNumber":149,"sourceCode":"\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\")\n}\n\n// GetPage retrieves a single page by ID.\nfunc (c *notionClient) GetPage(ctx context.Context, pageID string) (*notionPage, error) {\n\trespBody, err := c.doRequest(ctx, http.MethodGet, \"/v1/pages/\"+pageID, nil)","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/connector/notion/client.go#L131-L167","documentation":"After the retry loop in doRequest exhausts all attempts without a 2xx or an immediate error return, the accumulated lastErr (rate limited / server error / transport error) is wrapped with the sentinel datasource.ErrFetchFailed using %w so callers can errors.Is() against it. If lastErr is somehow nil (loop ended without setting it), bare ErrFetchFailed is returned. This is the generic 'fetch ultimately failed' terminal error for all Notion API calls.","triggerScenarios":"Any doRequest call where all 4 attempts fail identically: persistent 429s, persistent 5xx, or persistent transport errors (connection refused, DNS failure, timeouts) from Ping, GetPage, GetDatabaseInfo, GetDataSourceInfo, GetBlockChildrenFlat, or getBlockChildrenRecursive.","commonSituations":"Network egress blocked from the WeKnora host to api.notion.com (firewall, VPC without NAT); DNS resolution failures; Notion outage lasting longer than the ~7s retry window; ctx cancellation during backoff sleeps returns ctx.Err() instead, so this specifically fires for sustained failures.","solutions":["Unwrap with errors.Is(err, datasource.ErrFetchFailed) then inspect the wrapped cause to distinguish rate-limit vs 5xx vs network failure.","Verify network egress: curl https://api.notion.com/v1/users/me from the host to confirm connectivity and DNS.","Check proxy env vars (HTTPS_PROXY) are correct if the host requires egress through a proxy.","If persistent 429, reduce request volume or wait for the rate budget to reset.","If persistent 5xx, check status.notion.so for an incident and retry the sync later."],"exampleFix":"// before: opaque handling\nif err != nil { return err }\n// after: branch on the sentinel\nif errors.Is(err, datasource.ErrFetchFailed) {\n    log.Printf(\"notion fetch failed permanently: %v\", err)\n    // schedule retry / mark sync failed\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the token and network before long syncs\nif err := client.Ping(ctx); err != nil {\n    return fmt.Errorf(\"notion pre-flight failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, datasource.ErrFetchFailed) {\n    switch {\n    case strings.Contains(err.Error(), \"rate limited\"):\n        // back off and reschedule\n    case strings.Contains(err.Error(), \"server error\"):\n        // retry later, likely Notion incident\n    default:\n        // network problem: check egress/proxy/DNS\n    }\n}","preventionTips":["Always errors.Is() against datasource.ErrFetchFailed to identify terminal fetch failures","Run a Ping pre-flight before bulk operations","Confirm DNS/egress/proxy config on the deployment host","Make sync jobs resumable so terminal failures don't restart from scratch"],"tags":["notion","fetch-failed","sentinel-error","network","retry-exhausted"],"backgroundTag":"fetch-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}