gastownhall/beads · error
fetch parent %s: %w
Error message
fetch parent %s: %w
What it means
ReconcileParents fetches each link's parent issue from Linear via fetchIssue. When the fetch fails AND isRateLimitExhausted(err) reports the Linear API rate limit is fully exhausted, the function aborts the entire reconciliation immediately by returning this wrapped error instead of merely recording it in stats.Errors. The %w wrapping preserves the underlying rate-limit error for errors.Is/errors.As inspection.
Source
Thrown at internal/linear/parent_reconcile.go:194
if err != nil {
// Rate-limit circuit breaker tripped — stop now rather than
// hammer the API for every remaining link.
if isRateLimitExhausted(err) {
return stats, fmt.Errorf("fetch child %s: %w", link.ChildIdentifier, err)
}
stats.Errors = append(stats.Errors,
fmt.Errorf("fetch child %s: %w", link.ChildIdentifier, err))
continue
}
if childE.issue == nil {
stats.NotFound = append(stats.NotFound, link.ChildIdentifier)
continue
}
parentE, err := fetchIssue(link.ParentIdentifier)
if err != nil {
if isRateLimitExhausted(err) {
return stats, fmt.Errorf("fetch parent %s: %w", link.ParentIdentifier, err)
}
stats.Errors = append(stats.Errors,
fmt.Errorf("fetch parent %s: %w", link.ParentIdentifier, err))
continue
}
if parentE.issue == nil {
stats.NotFound = append(stats.NotFound, link.ParentIdentifier)
continue
}
// Idempotency: skip if remote parent already matches by UUID.
if childE.issue.Parent != nil && childE.issue.Parent.ID == parentE.issue.ID {
stats.Skipped++
continue
}
if dryRun {
// Dry-run: record the intended mutation but skip the API call.View on GitHub (pinned to 71377f2769)
Solutions
- Wait for the Linear rate-limit window to reset (inspect Retry-After / the underlying error) and re-run reconciliation
- Reduce request volume: batch or throttle fetchIssue calls, and run ReconcileParents less frequently
- Process links in smaller batches and persist progress so a rate-limit abort resumes rather than restarts
- Check for other processes or jobs sharing the same Linear API key and stagger them
Example fix
// before: tight loop aborting on limit
for _, link := range links { stats, err := ReconcileParents(ctx, link) }
// after: backoff between batches
if err != nil && isRateLimitExhausted(err) {
time.Sleep(backoff)
continue // resume from saved progress
} Defensive patterns
Strategy: retry
Validate before calling
// no pre-call validation possible; budget requests ahead of time
limiter := rate.NewLimiter(rate.Every(time.Second), 5)
if !limiter.Allow() { time.Sleep(time.Second) } Type guard
null
Try / catch
stats, err := ReconcileParents(ctx, links)
if err != nil && isRateLimitExhausted(err) {
wait, ok := retryAfterFrom(err) // inspect wrapped error
if !ok { wait = time.Minute }
time.Sleep(wait)
stats, err = ReconcileParents(ctx, remaining(links))
} Prevention
- Throttle Linear API calls with a client-side rate limiter sized below Linear's documented limits
- Persist reconciliation progress so aborted runs resume instead of restarting from zero
- Stagger jobs that share the same Linear API key
When it happens
Trigger: Calling ReconcileParents (via reconcileLinearParents) when a link.ParentIdentifier fetch hits Linear's API rate limit that is already exhausted (retries depleted), causing the early return path at the isRateLimitExhausted(err) branch.
Common situations: Bulk parent-link reconciliation over many issues in a short window; multiple sync workers sharing one Linear API key; a cron/daemon loop running ReconcileParents more often than the Linear rate budget allows.
Related errors
- batch create failed and recovery search also failed: %w (bat
- failed after %d retries: %w
- max retries (%d) exceeded: %w
- failed to parse update response: %w
- batch create failed; %d of %d issues unconfirmed (batch erro
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/20ede56ac67f05d9.
Report an issue: GitHub.