{"record":{"id":"4e79862c7f108506","repo":"App-vNext/Polly","slug":"the-operation-has-been-rate-limited-and-should-be","errorCode":null,"errorMessage":"The operation has been rate-limited and should be retried after {retryAfter}","messagePattern":"The operation has been rate-limited and should be retried after (.+?)","errorType":"exception","errorClass":"RateLimitRejectedException","httpStatus":null,"severity":"warning","filePath":"src/Polly/RateLimit/AsyncRateLimitEngine.cs","lineNumber":26,"sourceCode":"        Func<TimeSpan, Context, TResult>? retryAfterFactory,\n        Func<Context, CancellationToken, Task<TResult>> action,\n        Context context,\n        bool continueOnCapturedContext,\n        CancellationToken cancellationToken)\n    {\n        (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution();\n\n        if (permit)\n        {\n            return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext);\n        }\n\n        if (retryAfterFactory != null)\n        {\n            return retryAfterFactory(retryAfter, context);\n        }\n\n        throw new RateLimitRejectedException(retryAfter);\n    }\n}\n","sourceCodeStart":8,"sourceCodeEnd":29,"githubUrl":"https://github.com/App-vNext/Polly/blob/d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac/src/Polly/RateLimit/AsyncRateLimitEngine.cs#L8-L29","documentation":"Thrown by AsyncRateLimitEngine.ImplementationAsync when the configured rate limiter denies execution (PermitExecution returns false) and no retryAfterFactory was supplied. The RateLimitRejectedException carries a RetryAfter timespan computed by the token-bucket limiter indicating when retry becomes permissible. It is a deliberate rejection signal, not a bug.","triggerScenarios":"An AsyncRateLimitPolicy (without retryAfterFactory) executes a delegate while the token bucket is exhausted; PermitExecution returns (false, retryAfter), retryAfterFactory is null, so the engine throws RateLimitRejectedException(retryAfter). Spikes/bursts that exceed the configured numberOfExecutions/perTimeSpan trigger it.","commonSituations":"Traffic exceeding the configured rate; maxBurst set too low for startup storms; perTimeSpan/numberOfExecutions mis-sized for real load; forgetting to add a retry policy around the rate-limited call; a retryAfterFactory intended to convert rejection to a fallback value was omitted.","solutions":["Wrap the rate-limited policy in a Retry policy with backoff that respects RetryAfter to ride out throttling.","Provide a retryAfterFactory in RateLimitAsync to return a fallback TResult instead of throwing.","Increase numberOfExecutions and/or maxBurst after measuring actual throughput needs.","Catch RateLimitRejectedException at the call site and back off using exception.RetryAfter."],"exampleFix":"// before\nvar rl = Policy.RateLimitAsync(10, TimeSpan.FromSeconds(1), 10);\nawait rl.ExecuteAsync(ctx => doWork(ctx), new Context(), CancellationToken.None);\n\n// after\nvar retry = Policy.Handle<RateLimitRejectedException>()\n    .WaitAndRetryAsync((_, ex, _) =>\n        TimeSpan.FromSeconds(((RateLimitRejectedException)ex).RetryAfter.TotalSeconds + 0.1),\n        (_, _, _, _) => Task.CompletedTask);\nvar wrap = Policy.WrapAsync(retry, rl);\nawait wrap.ExecuteAsync(ctx => doWork(ctx), new Context(), CancellationToken.None);","handlingStrategy":"fallback","validationCode":"// Choose a strategy up front: either supply a retryAfterFactory or wrap in retry\nvar rl = Policy.RateLimitAsync<TResult>(10, TimeSpan.FromSeconds(1), 10,\n    (retryAfter, ctx) => /* fallback value */ default);\nawait rl.ExecuteAsync(func, context, ct);","typeGuard":null,"tryCatchPattern":"try { await rl.ExecuteAsync(func, context, ct); }\ncatch (RateLimitRejectedException ex) {\n    await Task.Delay(ex.RetryAfter.Add(TimeSpan.FromMilliseconds(50)), ct);\n    // retry or fall back\n}","preventionTips":["Always pair RateLimitAsync with a Retry policy that honors RetryAfter.","Size numberOfExecutions and maxBurst from real throughput measurements.","Consider a retryAfterFactory to convert throttling into a deliberate fallback value.","Monitor RateLimitRejectedException frequency to tune the limit."],"tags":["ratelimit","rejection","throttle","retryafter","legacy-api"],"backgroundTag":null,"analyzedSha":"d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac","analyzedAt":"2026-08-13T16:36:01.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}