Skip to content

INF-1671/fix: clamp and floor Retry-After in twoctl backoffDelay#2

Merged
brtkwr merged 1 commit into
mainfrom
brtkwr-two/inf-1671-twoctl-clamp-and-floor-retry-after
Jun 16, 2026
Merged

INF-1671/fix: clamp and floor Retry-After in twoctl backoffDelay#2
brtkwr merged 1 commit into
mainfrom
brtkwr-two/inf-1671-twoctl-clamp-and-floor-retry-after

Conversation

@brtkwr

@brtkwr brtkwr commented Jun 16, 2026

Copy link
Copy Markdown
Member

What

backoffDelay in internal/httpx/client.go now treats Retry-After as a lower bound clamped to maxBackoff, instead of honouring it literally. It always computes the jittered exponential backoff and uses max(backoff, retryAfter), with retryAfter clamped to maxBackoff.

Extracted two helpers (jitteredBackoff, retryAfter) so the policy reads clearly.

Why

Two latent bugs in the previous literal handling:

  1. No upper clamp. A hostile or misconfigured upstream returning Retry-After: 999999 made the CLI sleep ~11.5 days between retries (bounded only by a per-command context deadline, which not every command sets).
  2. Retry-After: 0 -> zero-delay retry. After a fleet-wide rate-limit reset, Retry-After: 0 collapsed backoff to ~0, producing a thundering herd and burning the retry budget instantly.

twoadm-cli's internal/httpx already guards both edges; this brings twoctl in line so the two CLIs keep the same retry semantics (the "same retry policy semantics across both ctls" line of INF-1314).

Acceptance criteria

  • Retry-After: 999999 -> delay == maxBackoff (no multi-day sleep). ✅ TestBackoffDelayClampsLargeRetryAfter
  • Retry-After: 0 -> delay >= baseBackoff (no zero-delay storm). ✅ TestBackoffDelayFloorsZeroRetryAfter
  • Retry-After: 7 -> still ~7s (below cap, unchanged). ✅ existing TestBackoffDelayHonoursRetryAfter
  • A small Retry-After never shrinks our backoff below the cap at high attempts. ✅ TestBackoffDelayRetryAfterIsLowerBoundNotCeiling

Scope

Stayed within the ticket boundary: only backoffDelay and its tests changed. HTTP-date form of Retry-After (twoadm-cli parses it, twoctl is seconds-only) is left as a possible follow-up, noted on the ticket.

Verification

go test ./... green (full module, including the pre-existing Retry-After: 0 fast-path test). gofmt/go vet clean, go build ./... ok. Run in the cloud sandbox with Go 1.26.3.

Fixes INF-1671
Refs INF-1314

Treat the Retry-After header as a lower bound clamped to maxBackoff rather
than an override. A hostile or misconfigured Retry-After: 999999 no longer
parks the CLI for days, and Retry-After: 0 after a rate-limit reset no longer
collapses into a zero-delay retry storm. Mirrors the twoadm-cli httpx
hardening so the two CLIs keep the same retry semantics (INF-1314).
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@brtkwr brtkwr merged commit d657a4f into main Jun 16, 2026
2 checks passed
@brtkwr brtkwr deleted the brtkwr-two/inf-1671-twoctl-clamp-and-floor-retry-after branch June 16, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants