Skip to content

Commit 84564e0

Browse files
mycollablabclaude
andcommitted
Add --max-retry and --max-retry-timer global flags for 429 rate limiting
Adds two new persistent flags to control retry behavior on HTTP 429 responses: - --max-retry (default 3): max number of retry attempts before giving up - --max-retry-timer (default 60s): max total seconds allowed across all retries If either limit would be exceeded before the next retry, the command exits immediately with a clear error message stating the throttle condition and how long to wait before retrying, rather than silently sleeping through it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a3e1bea commit 84564e0

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

cmd/root.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ var rootCmd = &cobra.Command{
3636
paginate, _ := cmd.Flags().GetBool("paginate")
3737
config.SetPaginate(paginate)
3838

39+
// Rate-limit retry controls
40+
maxRetry, _ := cmd.Flags().GetInt("max-retry")
41+
config.SetMaxRetry(maxRetry)
42+
maxRetryTimer, _ := cmd.Flags().GetInt("max-retry-timer")
43+
config.SetMaxRetryTimer(maxRetryTimer)
44+
3945
// Load app config early (safe local file read, needed by skipAuth-exempt commands like set-org)
4046
cfg, err := appconfig.Load()
4147
if err != nil {
@@ -172,6 +178,8 @@ func init() {
172178
rootCmd.PersistentFlags().Bool("dry-run", false, "Print write requests without executing them")
173179
rootCmd.PersistentFlags().String("user", "", "Use a specific authenticated user (email)")
174180
rootCmd.PersistentFlags().String("organization", "", "Override organization ID for this command")
181+
rootCmd.PersistentFlags().Int("max-retry", 3, "Max number of 429 retries before giving up (0 = no retries)")
182+
rootCmd.PersistentFlags().Int("max-retry-timer", 60, "Max total seconds to wait across all 429 retries (0 = unlimited)")
175183

176184
// On flag errors (unknown flag, bad value), print usage with valid flags.
177185
// SilenceUsage suppresses Cobra's automatic usage, so we print it ourselves.

internal/client/client.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,22 @@ func Do(req *Request) ([]byte, int, error) {
3232
body, status, headers, err = doOnce(req)
3333
}
3434

35-
for retries := 0; status == 429 && retries < 3; retries++ {
35+
var cumulativeWait time.Duration
36+
for retries := 0; status == 429; retries++ {
37+
maxRetry := config.MaxRetry()
38+
maxTimer := time.Duration(config.MaxRetryTimer()) * time.Second
3639
wait := retryAfterDuration(headers.Get("Retry-After"))
37-
fmt.Fprintf(os.Stderr, "Rate limited (429). Retrying in %s...\n", wait.Round(time.Second))
40+
41+
if retries >= maxRetry {
42+
return body, status, fmt.Errorf("rate limited (429): retry limit reached after %d attempts — try again after %s", retries, wait.Round(time.Second))
43+
}
44+
if maxTimer > 0 && cumulativeWait+wait > maxTimer {
45+
return body, status, fmt.Errorf("rate limited (429): retry wait of %s would exceed max-retry-timer of %s — try again after %s", (cumulativeWait + wait).Round(time.Second), maxTimer.Round(time.Second), wait.Round(time.Second))
46+
}
47+
48+
fmt.Fprintf(os.Stderr, "Rate limited (429). Retrying in %s (attempt %d/%d)...\n", wait.Round(time.Second), retries+1, maxRetry)
3849
time.Sleep(wait)
50+
cumulativeWait += wait
3951
body, status, headers, err = doOnce(req)
4052
}
4153

internal/config/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@ var (
1313
orgID string // UUID format
1414
orgIDBase64 string // base64 Webex format
1515
TokenRefresher func() (string, error)
16+
17+
maxRetry int // max number of 429 retries (0 = no retries)
18+
maxRetryTimer int // max total seconds allowed across all 429 waits (0 = unlimited)
1619
)
1720

1821
func SetToken(t string) { token = t }
1922
func Token() string { return token }
2023

24+
func SetMaxRetry(n int) { maxRetry = n }
25+
func MaxRetry() int { return maxRetry }
26+
27+
func SetMaxRetryTimer(secs int) { maxRetryTimer = secs }
28+
func MaxRetryTimer() int { return maxRetryTimer }
29+
2130
func SetDebug(d bool) { debug = d }
2231
func Debug() bool { return debug }
2332

0 commit comments

Comments
 (0)