Skip to content

fix: improve rate limit recovery - #2200

Merged
liangshuo-1 merged 1 commit into
mainfrom
fix/tat-rate-limit-recovery
Aug 5, 2026
Merged

fix: improve rate limit recovery#2200
liangshuo-1 merged 1 commit into
mainfrom
fix/tat-rate-limit-recovery

Conversation

@liangshuo-1

@liangshuo-1 liangshuo-1 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Improve rate-limit error recovery while keeping the change narrowly scoped.

Changes

  • Return structured retry guidance for rate-limited requests.
  • Preserve upstream retry timing when available.
  • Add contract and regression coverage.

Test Plan

  • Relevant unit tests pass
  • Build and static checks pass

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • API errors can now include a retry_after_seconds value to indicate when a retry may be appropriate.
    • HTTP 429 rate-limit responses preserve retry timing from supported response headers or provide fallback backoff guidance.
    • Retryable rate-limit errors are now distinguishable from other transient errors.
  • Documentation

    • Added guidance on interpreting retry timing, exponential backoff with jitter, and cases where no delay is available.
    • Clarified that automatic retries are not performed and HTTP 429 handling currently applies to TAT fetching.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds RetryAfterSeconds to APIError, serializes it when nonzero, and documents its contract. FetchTAT now classifies HTTP 429 responses as typed retryable errors and derives retry delays from gateway or standard headers.

Changes

TAT rate-limit metadata

Layer / File(s) Summary
API error contract and serialization
errs/types.go, errs/marshal_test.go, errs/ERROR_CONTRACT.md, internal/recovery/render_test.go
APIError now exposes optional RetryAfterSeconds metadata. Serialization, rendering, cloning, omission, and consumer guidance cover the new field.
TAT HTTP 429 classification and retry parsing
internal/credential/tat_fetch.go
FetchTAT parses TAT responses, classifies HTTP 429 errors as typed retryable rate-limit errors, and reads retry delays from X-Ogw-Ratelimit-Reset or Retry-After.
HTTP 429 and OAuth error test coverage
internal/credential/tat_fetch_test.go
Tests simulate response headers and validate typed HTTP 429 errors, retry metadata, backoff guidance, and untyped OAuth slow_down handling.

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
Loading

Possibly related PRs

  • larksuite/cli#2193: Modifies the same error and TAT-fetching code for typed HTTP 429 retry metadata.

Suggested labels: bug

Suggested reviewers: evandance

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: improving rate-limit error recovery by adding structured retry guidance and header parsing.
Description check ✅ Passed The description includes all required template sections: Summary, Changes, Test Plan with checkboxes, and Related Issues. Content is specific and complete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tat-rate-limit-recovery

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size/M Single-domain feat or fix with limited business impact label Aug 5, 2026
@liangshuo-1
liangshuo-1 marked this pull request as ready for review August 5, 2026 16:16
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

  • security — cancelled — details
  • sidecar-integration — cancelled — details
  • deadcode — cancelled — details
  • unit-test — cancelled — details
  • plugin-integration — cancelled — details
  • deterministic-gate — cancelled — details
  • coverage — cancelled — details
  • lint — cancelled — details
  • e2e-dry-run — cancelled — details
  • e2e-live — cancelled — details
  • results — failure — details

deterministic-gate

  • System issue: deterministic-gate failed, but quality-gate facts were unavailable. expected exactly one base-bound quality gate facts artifact, got 0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 960bdf6 and 57ad1c2.

📒 Files selected for processing (6)
  • errs/ERROR_CONTRACT.md
  • errs/marshal_test.go
  • errs/types.go
  • internal/credential/tat_fetch.go
  • internal/credential/tat_fetch_test.go
  • internal/recovery/render_test.go

Comment on lines +185 to 242
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)
}
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@57ad1c2f69dd8169bb721ce627e33c43637e9ced

🧩 Skill update

npx skills add larksuite/cli#fix/tat-rate-limit-recovery -y -g

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.04%. Comparing base (960bdf6) to head (57ad1c2).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@liangshuo-1
liangshuo-1 merged commit 43fe6c8 into main Aug 5, 2026
33 of 44 checks passed
@liangshuo-1
liangshuo-1 deleted the fix/tat-rate-limit-recovery branch August 5, 2026 16:32
@liangshuo-1 liangshuo-1 mentioned this pull request Aug 5, 2026
3 tasks
zkh-bytedance pushed a commit that referenced this pull request Aug 6, 2026
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant