Skip to content

fix: override rate limit retry to 60s for per-user API calls - #86

Merged
btipling merged 1 commit into
mainfrom
bt/rate-limit-60s-override
Apr 7, 2026
Merged

fix: override rate limit retry to 60s for per-user API calls#86
btipling merged 1 commit into
mainfrom
bt/rate-limit-60s-override

Conversation

@btipling

@btipling btipling commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • When users.info returns a 429, overrides the rate limit annotation to 60s instead of Slack's Retry-After: 4s
  • Only applies the override when the error is actually a rate limit (checked via IsRateLimited)
  • Applied to both usergroup grants per-member loop and SCIM user listing per-user loop
  • Adds IsRateLimited and RateLimitOverride helpers with tests

Context

Resolves CXH-1301

Slack's users.info Retry-After header returns 4s, but the endpoint has a rolling window of ~100 req/min. Waiting only 4s recovers enough quota for 1-2 requests before hitting the limit again, causing an infinite retry loop. 60s lets the window fully reset.

Test plan

  • Unit tests pass: go test ./pkg/connector/client/ -v
  • Run sync against workspace with large usergroups — verify grants eventually complete instead of looping on 429s
  • Verify non-rate-limit errors are unaffected (no 60s override applied)

🤖 Generated with Claude Code

Slack's Retry-After header returns 4s for users.info 429s, but the
endpoint has a rolling window of ~100 req/min. Waiting only 4s recovers
enough quota for 1-2 requests before hitting the limit again, causing
an infinite retry loop that never makes progress.

When WrapError detects a rate limit error (via IsRateLimited check on
the annotations), override the annotation to 60s so the SDK waits long
enough for the rolling window to fully reset. Applied to both the
usergroup grants per-member loop and SCIM user listing per-user loop.

Also removes double-wrapping of errors in scimUserResource — the caller
in listScimAPI now handles WrapError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented Apr 7, 2026

Copy link
Copy Markdown

@btipling
btipling requested a review from a team April 7, 2026 20:37
@github-actions

github-actions Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @btipling's task in 1m 6s —— View job


PR Review: fix: override rate limit retry to 60s for per-user API calls

  • Gather context and read changed files
  • Review code changes
  • Post review feedback

Overall: This PR looks good. The approach is sound — Slack's Retry-After: 4s on users.info is misleading given the ~100 req/min rolling window, and overriding to 60s is a reasonable fix.

What I like

  • Clean separation of helpers: IsRateLimited and RateLimitOverride are well-scoped and testable.
  • Guard on IsRateLimited: The override only applies when the error is actually a rate limit, not on other errors. This is correct.
  • Good test coverage: Tests cover nil annotations, empty annotations, overlimit, OK status, WrapError integration, and the override-replaces-existing behavior.
  • Consistent application: Both call sites (SCIM user listing in user.go:248-249 and usergroup grants in user_group.go:170-172) follow the same pattern.

Suggestions

  1. Consider making the 60s duration a named constant (pkg/connector/client/helpers.go:207)

    RateLimitOverride hardcodes 60 * time.Second. Since defaultRateLimitRetryAfter (line 25) already sets a precedent for named durations, a constant like userInfoRateLimitRetryAfter would make the value easier to find and tune later.

    This is a minor nit — the function comment already explains the 60s, so this is fine as-is too.

  2. The parentResourceID passed to userResource in user_group.go:175 is res.Id (the group's own ID), not the workspace ID.

    This is pre-existing behavior, not introduced by this PR — just flagging it since I noticed it while reading the grant flow. The user resource gets the group as its parent rather than the workspace.

  3. Test timing sensitivity (helpers_test.go:80-83)

    The test checks resetIn is between 55s and 65s. This is generous enough that it should never flake in practice, but worth noting if CI ever runs on extremely slow VMs.

No issues found

  • No bugs, security issues, or correctness problems.
  • The error propagation is correct: wrappedErr is returned after the annotation override, so the SDK sees the 60s reset time.
  • The WithRateLimiting call replaces (not appends) the existing rate limit annotation, which is the desired behavior — confirmed by TestRateLimitOverrideReplacesExisting.

LGTM — this is a clean, focused fix.

@btipling
btipling merged commit 80939fb into main Apr 7, 2026
10 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.

1 participant