{"record":{"id":"2a9a930a37b4792d","repo":"gastownhall/beads","slug":"aborting-push-to-s-provider-rate-limit-hit-s","errorCode":null,"errorMessage":"Aborting push to %s: provider rate limit hit (%s); %d issue(s) skipped — retry after the cooldown","messagePattern":"Aborting push to (.+?): provider rate limit hit \\((.+?)\\); (.+?) issue\\(s\\) skipped — retry after the cooldown","errorType":"console","errorClass":"RateLimitedError","httpStatus":null,"severity":"warning","filePath":"internal/tracker/errors.go","lineNumber":27,"sourceCode":"// RateLimitedError is implemented by provider errors when the upstream API\n// has rate-limited the request. The push loop uses errors.As against this\n// interface to detect rate limiting without importing any provider package.\ntype RateLimitedError interface {\n\terror\n\t// RateLimitRetryAfter returns the wait before retrying. Zero means\n\t// \"the server didn't say\".\n\tRateLimitRetryAfter() time.Duration\n}\n\nfunc isRateLimitedErr(err error) bool {\n\tvar rl RateLimitedError\n\treturn errors.As(err, &rl)\n}\n\n// warnRateLimitAbort emits the standard \"we hit a provider rate limit, the\n// rest of the queue is left for next sync\" message.\nfunc (e *Engine) warnRateLimitAbort(err error, remaining int) {\n\te.warn(\"Aborting push to %s: provider rate limit hit (%s); %d issue(s) skipped — retry after the cooldown\",\n\t\te.Tracker.DisplayName(), formatRateLimitWait(err), remaining)\n}\n\nfunc formatRateLimitWait(err error) string {\n\tvar rl RateLimitedError\n\tif !errors.As(err, &rl) {\n\t\treturn \"unknown\"\n\t}\n\td := rl.RateLimitRetryAfter()\n\tif d <= 0 {\n\t\treturn \"unknown\"\n\t}\n\treturn fmt.Sprintf(\"retry after %s\", d.Round(time.Second))\n}\n","sourceCodeStart":9,"sourceCodeEnd":42,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/tracker/errors.go#L9-L42","documentation":"During a push, when the provider API returns a rate-limit error (an error implementing tracker.RateLimitedError), the engine aborts the remaining queue rather than hammering the API, warns with this message, and leaves the remaining issues for the next sync. The message includes the provider name, the retry-after hint (or 'unknown' if the server didn't say), and how many issues were skipped.","triggerScenarios":"`bd push` (doPush) to an external tracker (GitHub, GitLab, Jira, Linear, etc.) that responds with HTTP 429 / rate-limit errors; the engine detects it via errors.As(err, &RateLimitedError) and calls warnRateLimitAbort(err, remaining).","commonSituations":"Initial sync of a large backlog exceeding the provider's per-hour quota; CI jobs pushing on a shared token that has exhausted its rate limit; unauthenticated or low-tier tokens with small limits; bulk imports triggering secondary rate limits.","solutions":["Wait for the cooldown shown in the message (RateLimitRetryAfter), then re-run the push; the remaining issues will be retried.","Use a token with a higher rate limit or dedicated to this automation (avoid shared CI tokens).","Reduce batch size / throttle pushes (push in smaller batches spaced over time).","Ensure the provider client is authenticating — anonymous limits are far lower.","If the provider reports 'unknown' wait, check its dashboard or Retry-After headers for the actual reset time."],"exampleFix":"// before: bulk push blows through GitHub's limit\nbd push  # Aborting push to github: provider rate limit hit (retry after 12m0s); 340 issue(s) skipped\n// after: wait out the cooldown or raise the limit, then retry\nbd push  # with higher-limit token, after cooldown","handlingStrategy":"retry","validationCode":"// Before pushing, check remaining quota if the provider exposes it:\n// e.g. GitHub: curl -s -H \"Authorization: Bearer $TOKEN\" https://api.github.com/rate_limit | jq .resources.core","typeGuard":"// Detect rate limiting the same way the engine does:\nvar rl tracker.RateLimitedError\nif errors.As(err, &rl) {\n    wait := rl.RateLimitRetryAfter()\n    // schedule retry after `wait`\n}","tryCatchPattern":"// Retry loop honoring the provider's retry-after:\nfor {\n    err := engine.Push(ctx)\n    if err == nil { break }\n    var rl tracker.RateLimitedError\n    if errors.As(err, &rl) {\n        d := rl.RateLimitRetryAfter()\n        if d <= 0 { d = time.Minute }\n        time.Sleep(d)\n        continue\n    }\n    return err\n}","preventionTips":["Use a dedicated, authenticated token with sufficient rate limits for automation.","Batch or throttle pushes; avoid full-backlog pushes on fresh installations during peak windows.","Schedule syncs spaced out in CI rather than on every commit.","Respect the reported cooldown — immediate retries usually extend the limit.","Monitor provider rate-limit headers/quota dashboards in automation."],"tags":["rate-limit","api","push","retry","backoff"],"backgroundTag":"api-rate-limit-exceeded","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}