Skip to content

fix(sdk/python): honor numeric Retry-After minimum waits - #1129

Merged
AbirAbbas merged 4 commits into
Agent-Field:mainfrom
remote-controlled-man:codex/prflow-5f53e274f7ba
Oct 30, 2026
Merged

AbirAbbas merged 4 commits into
Agent-Field:mainfrom
remote-controlled-man:codex/prflow-5f53e274f7ba

Conversation

@remote-controlled-man

Copy link
Copy Markdown
Contributor

Summary

The Python SDK can retry before a provider's numeric Retry-After cooldown ends: the default jitter can turn a 10-second hint into 7.5 seconds, and hints above rate_limit_max_delay are discarded. Treat finite positive hints as minimum waits, retaining jitter without retrying early; invalid hints fall back to ordinary exponential backoff.

This deliberately allows a provider-requested wait above the local exponential-backoff cap. The configuration description now makes that distinction explicit. Five parameterized regression cases exercise early retries, above-cap hints, and negative/NaN/infinite hints.

Type of change

  • Bug fix

Test plan

Executed the five test stages from scripts/test-all.sh individually, split across macOS (Python/Node) and the existing Linux Go environment; this is not a claim that one invocation of the script passed on macOS.

  • Python SDK: python -m pytest with the unchanged default coverage/marker configuration — 2,809 passed, 4 skipped, 38 deselected; displayed aggregate coverage 95%. This full run preceded an AST-identical formatter-only commit; 28 rate-limiter/config tests passed again on the final commit.
  • Python Ruff lint, Mypy (86 source files), and Ruff formatting check of all three changed files passed.
  • Control plane and Go SDK: go test ./... in both modules, Linux arm64 / Go 1.25.14, GOFLAGS="-tags=sqlite_fts5 -p=2"; both passed. go mod tidy -diff produced no changes.
  • TypeScript SDK: CI=1 npm run test:core — 1,256 passed, 1 skipped.
  • Web UI: CI=1 npm run test — 837 passed.

Linux checks ran as a non-root user with real DNS packets forwarded over certificate-validated HTTPS because this host rewrites reserved domains. The Go SDK used a complete writable temporary module copy whose 220 files matched the final commit; no test assertions, timeouts, or Go source overlays were changed. Earlier executor setup failures were corrected before the successful runs.

One earlier full Python run also hit an unrelated existing 1-second harness timeout. Focused, module, collection-prefix, and the latest full run passed unchanged; its root cause remains unproven.

Test coverage

  • Ran the changed surface locally with default coverage.
  • New code paths have regression tests.
  • Coverage gate green in upstream CI (pending).
  • No coverage baseline was changed; the four default Python skips and 38 deselections remain in effect.

Checklist

  • Read the contribution and development guidance.
  • Commits are signed and follow conventional-commits style (messages are conventional; the two commits are unsigned).
  • No public API signature or dependency changes.

Related issues / PRs

Related context: #265 established the local backoff cap; #1078 isolated the jitter RNG. This change preserves the private RNG and clarifies how provider cooldowns interact with the cap.

Copilot AI balanced review requested due to automatic review settings October 26, 2026 17:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Performance

SDK Memory Δ Latency Δ Tests Status
Python 9.1 KB - 0.31 µs -11% ✓ ✓

✓ No regressions detected

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 88.20% 87.40% ↑ +0.80 pp 🟡
sdk-go 93.70% 92.00% ↑ +1.70 pp 🟢
sdk-python 94.73% 93.73% ↑ +1.00 pp 🟢
sdk-typescript 92.50% 90.42% ↑ +2.08 pp 🟢
web-ui 84.89% 84.79% ↑ +0.10 pp 🟡
aggregate 86.11% 85.75% ↑ +0.36 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 0 — ➖ no changes
sdk-go 0 — ➖ no changes
sdk-python 7 100.00% ✅
sdk-typescript 0 — ➖ no changes
web-ui 0 — ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

@santoshkumarradha santoshkumarradha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. The retry delay now honors a valid server cooldown without breaking the normal backoff path, and the focused tests cover both valid and invalid values.

AbirAbbas and others added 2 commits October 30, 2026 09:17
Treating every finite Retry-After as a minimum let a provider
suspend each retry indefinitely: a 86400s hint made the default
five retries sleep five days, with no outer deadline on direct
Agent.ai calls. Honour hints up to max(rate_limit_max_delay, 300s)
and clamp anything larger to that ceiling, so jitter still never
retries before the (capped) cooldown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Contributor

Pushed one commit on top since this had been waiting a few days. Treating a valid Retry-After as a minimum is right, but nothing bounded it: Retry-After: 86400 made the default five retries sleep five days, and direct Agent.ai calls have no outer deadline around the limiter. Hints are now honoured up to max(rate_limit_max_delay, 300s) and anything larger is clamped to that, so jitter still never retries before the (capped) cooldown. New parametrized cases cover 3600s at both jitter ends and a larger configured max_delay. I also merged main. Full Python suite and ruff 0.15.22 are clean locally.

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the Retry-After ceiling commit (my push dismissed the earlier approval). Without the ceiling, the new cases fail with 3600s and 4500s sleeps. The full Python suite (2931 passed) and ruff 0.15.22 are clean, and CI is green.

@AbirAbbas
AbirAbbas added this pull request to the merge queue Oct 30, 2026
Merged via the queue into Agent-Field:main with commit db7d3de Oct 30, 2026
24 checks passed
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.

4 participants