fix: improve rate limit recovery - #2200
Conversation
📝 WalkthroughWalkthroughThe change adds ChangesTAT rate-limit metadata
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant FetchTAT
participant TATEndpoint
participant APIError
FetchTAT->>TATEndpoint: Request TAT
TATEndpoint-->>FetchTAT: HTTP 429 with error body and headers
FetchTAT->>APIError: Create typed retryable rate-limit error
FetchTAT->>APIError: Attach retry delay and backoff hint
APIError-->>FetchTAT: Return API error
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Quality SummaryCI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun. Failed checks
deterministic-gate
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/credential/tat_fetch_test.go`:
- Around line 185-242: Update TestFetchTAT_HTTP429_TypedRateLimit to assert
typed metadata with errs.ProblemOf, specifically CategoryAPI and
SubtypeRateLimit, instead of only checking the raw APIError fields. Extend the
table in that test to cover invalid or nonpositive X-Ogw-Ratelimit-Reset with a
valid Retry-After fallback, and cases where both headers are invalid so the
delay is omitted; keep the existing HTTP 429 scenarios and verify the documented
fallback/omission behavior through FetchTAT and errs.APIError.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0fa6ace8-71ef-43e7-9d2f-647b492709b9
📒 Files selected for processing (6)
errs/ERROR_CONTRACT.mderrs/marshal_test.goerrs/types.gointernal/credential/tat_fetch.gointernal/credential/tat_fetch_test.gointernal/recovery/render_test.go
| func TestFetchTAT_HTTP429_TypedRateLimit(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| body string | ||
| header http.Header | ||
| wantCode int | ||
| wantDelay int | ||
| }{ | ||
| {"http 429", 429, `{"code":99991400,"error":"too_many_requests","error_description":"rate limit exceeded"}`}, | ||
| {"oauth slow_down", 200, `{"error":"slow_down","error_description":"polling too fast"}`}, | ||
| { | ||
| name: "platform envelope", | ||
| body: `{"code":99991400,"error":"too_many_requests","error_description":"rate limit exceeded"}`, | ||
| header: http.Header{"X-Ogw-Ratelimit-Reset": []string{"8"}, "Retry-After": []string{"4"}}, | ||
| wantCode: 99991400, | ||
| wantDelay: 8, | ||
| }, | ||
| { | ||
| name: "standard retry-after fallback", | ||
| body: `{"error":"too_many_requests"}`, | ||
| header: http.Header{"Retry-After": []string{"4"}}, | ||
| wantCode: http.StatusTooManyRequests, | ||
| wantDelay: 4, | ||
| }, | ||
| { | ||
| name: "non-JSON gateway response", | ||
| body: "rate limit exceeded", | ||
| wantCode: http.StatusTooManyRequests, | ||
| wantDelay: 0, | ||
| }, | ||
| } | ||
| for _, tc := range cases { | ||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| rt := &stubRoundTripper{respCode: tc.code, respBody: tc.body} | ||
| rt := &stubRoundTripper{ | ||
| respCode: http.StatusTooManyRequests, | ||
| respBody: tc.body, | ||
| respHeader: tc.header, | ||
| } | ||
| hc := &http.Client{Transport: rt} | ||
|
|
||
| _, err := FetchTAT(context.Background(), hc, core.BrandFeishu, "cli_app", "secret_x") | ||
| if err == nil { | ||
| t.Fatal("expected error for rate-limit") | ||
| var apiErr *errs.APIError | ||
| if !errors.As(err, &apiErr) { | ||
| t.Fatalf("HTTP 429 error = %T %v, want *errs.APIError", err, err) | ||
| } | ||
| if errs.IsTyped(err) { | ||
| t.Errorf("rate-limit must be UNTYPED (transient), got typed %T %v", err, err) | ||
| if apiErr.Subtype != errs.SubtypeRateLimit || !apiErr.Retryable { | ||
| t.Fatalf("problem = %+v, want retryable api/rate_limit", apiErr.Problem) | ||
| } | ||
| if apiErr.Code != tc.wantCode { | ||
| t.Fatalf("code = %d, want %d", apiErr.Code, tc.wantCode) | ||
| } | ||
| if apiErr.RetryAfterSeconds != tc.wantDelay { | ||
| t.Fatalf("retry_after_seconds = %v, want %d", apiErr.RetryAfterSeconds, tc.wantDelay) | ||
| } | ||
| if !strings.Contains(apiErr.Hint, "exponential backoff with jitter") { | ||
| t.Fatalf("hint = %q, want backoff guidance", apiErr.Hint) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Complete the HTTP 429 contract assertions.
Use errs.ProblemOf to assert CategoryAPI and SubtypeRateLimit. Add cases where X-Ogw-Ratelimit-Reset is invalid or nonpositive and Retry-After is valid, plus cases where both headers are invalid. These cases protect the documented fallback and omission behavior.
As per coding guidelines, error-path tests must assert typed metadata through errs.ProblemOf, and every behavior change must have an accompanying direct test.
Proposed test additions
+ problem, ok := errs.ProblemOf(err)
+ if !ok || problem.Category != errs.CategoryAPI || problem.Subtype != errs.SubtypeRateLimit {
+ t.Fatalf("problem = %+v, want api/rate_limit", problem)
+ }+ {
+ name: "invalid gateway delay uses retry-after fallback",
+ body: `{"error":"too_many_requests"}`,
+ header: http.Header{"X-Ogw-Ratelimit-Reset": []string{"invalid"}, "Retry-After": []string{"4"}},
+ wantCode: http.StatusTooManyRequests,
+ wantDelay: 4,
+ },
+ {
+ name: "invalid delays are omitted",
+ body: `{"error":"too_many_requests"}`,
+ header: http.Header{"X-Ogw-Ratelimit-Reset": []string{"0"}, "Retry-After": []string{"-1"}},
+ wantCode: http.StatusTooManyRequests,
+ wantDelay: 0,
+ },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/credential/tat_fetch_test.go` around lines 185 - 242, Update
TestFetchTAT_HTTP429_TypedRateLimit to assert typed metadata with
errs.ProblemOf, specifically CategoryAPI and SubtypeRateLimit, instead of only
checking the raw APIError fields. Extend the table in that test to cover invalid
or nonpositive X-Ogw-Ratelimit-Reset with a valid Retry-After fallback, and
cases where both headers are invalid so the delay is omitted; keep the existing
HTTP 429 scenarios and verify the documented fallback/omission behavior through
FetchTAT and errs.APIError.
Source: Coding guidelines
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@57ad1c2f69dd8169bb721ce627e33c43637e9ced🧩 Skill updatenpx skills add larksuite/cli#fix/tat-rate-limit-recovery -y -g |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2200 +/- ##
=======================================
Coverage 76.03% 76.04%
=======================================
Files 966 966
Lines 102644 102667 +23
=======================================
+ Hits 78050 78075 +25
+ Misses 18689 18688 -1
+ Partials 5905 5904 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com> Co-authored-by: TRAE CLI <traecli@bytedance.com>
Summary
Improve rate-limit error recovery while keeping the change narrowly scoped.
Changes
Test Plan
Related Issues
Summary by CodeRabbit
New Features
retry_after_secondsvalue to indicate when a retry may be appropriate.Documentation