{"record":{"id":"e9f0e72796fc5cd0","repo":"gastownhall/beads","slug":"fetch-child-s-w","errorCode":null,"errorMessage":"fetch child %s: %w","messagePattern":"fetch child (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/linear/parent_reconcile.go","lineNumber":180,"sourceCode":"\t\t\treturn entry{}, err\n\t\t}\n\t\te := entry{issue: issue, client: client}\n\t\t// Cache nil too — repeated lookups are still cheap.\n\t\tfetched[identifier] = e\n\t\treturn e, nil\n\t}\n\n\tfor _, link := range links {\n\t\tif link.ChildIdentifier == \"\" || link.ParentIdentifier == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tchildE, err := fetchIssue(link.ChildIdentifier)\n\t\tif err != nil {\n\t\t\t// Rate-limit circuit breaker tripped — stop now rather than\n\t\t\t// hammer the API for every remaining link.\n\t\t\tif isRateLimitExhausted(err) {\n\t\t\t\treturn stats, fmt.Errorf(\"fetch child %s: %w\", link.ChildIdentifier, err)\n\t\t\t}\n\t\t\tstats.Errors = append(stats.Errors,\n\t\t\t\tfmt.Errorf(\"fetch child %s: %w\", link.ChildIdentifier, err))\n\t\t\tcontinue\n\t\t}\n\t\tif childE.issue == nil {\n\t\t\tstats.NotFound = append(stats.NotFound, link.ChildIdentifier)\n\t\t\tcontinue\n\t\t}\n\n\t\tparentE, err := fetchIssue(link.ParentIdentifier)\n\t\tif err != nil {\n\t\t\tif isRateLimitExhausted(err) {\n\t\t\t\treturn stats, fmt.Errorf(\"fetch parent %s: %w\", link.ParentIdentifier, err)\n\t\t\t}\n\t\t\tstats.Errors = append(stats.Errors,\n\t\t\t\tfmt.Errorf(\"fetch parent %s: %w\", link.ParentIdentifier, err))\n\t\t\tcontinue","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/parent_reconcile.go#L162-L198","documentation":"During ReconcileParents, fetching a child issue failed with a rate-limit-exhausted error, tripping the circuit breaker. Instead of continuing to hammer the API for every remaining link, the loop aborts immediately and returns this wrapped error alongside partial stats.","triggerScenarios":"fetchIssue(link.ChildIdentifier) returns an error for which isRateLimitExhausted(err) is true while reconciling parent links in ReconcileParents.","commonSituations":"Large backlogs of unlinked issues pushed through reconciliation in a short window; shared API token exhausted by other integrations; missing backoff between reconciliation runs.","solutions":["Wait for the rate-limit window to reset, then re-run ReconcileParents (it resumes on remaining links).","Reduce concurrency/batch size or spread reconciliation across intervals.","Use a dedicated API token for reconciliation so other workloads don't drain the quota.","Honor any Retry-After guidance and add longer backoff before re-running."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"stats, err := linear.ReconcileParents(ctx, deps)\nif err != nil && isRateLimitExhausted(err) {\n    time.Sleep(rateLimitResetWindow)\n    stats, err = linear.ReconcileParents(ctx, deps)\n}","preventionTips":["Schedule reconciliation runs with generous spacing.","Dedicate an API token to reconciliation jobs.","Cap batch sizes and stop early on 429 responses."],"tags":["linear","rate-limit","circuit-breaker","go"],"backgroundTag":"api-rate-limit-exceeded","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}