Problem / Motivation
The in-memory rate limiter (internal/ratelimit/rate_limiter.go, CheckRate) correctly resolves the most-restrictive limit across key, team, and org scopes — but then applies that single resolved limit to all three scope counters. This means a key with requests_per_minute: 5 inside an org with requests_per_minute: 1000 causes the org counter to be capped at 5, so every other key in that org is throttled far too aggressively and requests are rejected with 429 that should be allowed.
The Redis checker (internal/ratelimit/redis_checker.go) and the token counter do this correctly: each scope is checked against its own limit. The two checker implementations therefore have inconsistent semantics — switching from in-memory to Redis changes rate-limiting behavior.
Proposed Solution
Each scope (key, team, org) should be incremented and checked against its own configured limit independently. Most-restrictive-wins is preserved automatically: a request must pass all scopes, so the tightest limit still wins without polluting the counters of wider scopes.
Acceptance Criteria
To reproduce (in-memory path, no Redis configured):
- Create an org with
requests_per_minute: 1000 and two API keys each with requests_per_minute: 5
- Send 5 requests through key A (all succeed)
- Send requests through key B — observed: key B is rejected far earlier than its own limit of 5 because the org counter was capped at 5 instead of 1000
A fix is in progress.
Problem / Motivation
The in-memory rate limiter (
internal/ratelimit/rate_limiter.go,CheckRate) correctly resolves the most-restrictive limit across key, team, and org scopes — but then applies that single resolved limit to all three scope counters. This means a key withrequests_per_minute: 5inside an org withrequests_per_minute: 1000causes the org counter to be capped at 5, so every other key in that org is throttled far too aggressively and requests are rejected with 429 that should be allowed.The Redis checker (
internal/ratelimit/redis_checker.go) and the token counter do this correctly: each scope is checked against its own limit. The two checker implementations therefore have inconsistent semantics — switching from in-memory to Redis changes rate-limiting behavior.Proposed Solution
Each scope (key, team, org) should be incremented and checked against its own configured limit independently. Most-restrictive-wins is preserved automatically: a request must pass all scopes, so the tightest limit still wins without polluting the counters of wider scopes.
Acceptance Criteria
CheckRatein the in-memory implementation checks each scope against its own limit, not the resolved minimumrequests_per_minute: 1000and two keys each withrequests_per_minute: 5: after key A exhausts its 5 RPM, key B can still send up to its own 5 RPM without being rejected earlyTo reproduce (in-memory path, no Redis configured):
requests_per_minute: 1000and two API keys each withrequests_per_minute: 5A fix is in progress.