Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx agentmods add rules/pauljphilp/effectpatterns/handle-rate-limiting-responsesgit clone --depth 1 https://github.com/PaulJPhilp/EffectPatternsWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/rules/pauljphilp/effectpatterns/handle-rate-limiting-responses)<a href="https://agentmods.dev/rules/pauljphilp/effectpatterns/handle-rate-limiting-responses"><img src="https://agentmods.dev/badge/rules/pauljphilp/effectpatterns/handle-rate-limiting-responses.svg" alt="Measured on agentmods" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5 | $0.01598 | $0.01598 |
| Opus 5 | $0.00799 | $0.00799 |
| Sonnet 5 | $0.00320 | $0.00320 |
| Haiku 4.5 | $0.00160 | $0.00160 |
Grade A, and why
handle-rate-limiting-responses scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 244 lines — stays where its author put it; the contents beside it link to each section on GitHub.
description: Detect 429 responses and automatically retry after the Retry-After period. globs: "**/*.ts" alwaysApply: true
Handle Rate Limiting Responses
Rule: Detect 429 responses and automatically retry after the Retry-After period.
Example
import { Effect, Schedule, Duration, Data, Ref } from "effect"
import { HttpClient, HttpClientResponse } from "@effect/platform"
// ============================================
// 1. Rate limit error type
// ============================================
class RateLimitedError extends Data.TaggedError("RateLimitedError")<{
readonly retryAfter: number
readonly limit: number | undefined
readonly remaining: number | undefined
readonly reset: number | undefined
}> {}
// ============================================
// 2. Parse rate limit headers
// ============================================
interface RateLimitInfo {
readonly retryAfter: number
readonly limit?: number
readonly remaining?: number
readonly reset?: number
}
const parseRateLimitHeaders = (headers: Record<string, string>): RateLimitInfo => {
// Parse Retry-After (seconds or date)
const retryAfterHeader = headers["retry-after"]
let retryAfter = 60 // Default 60 seconds
if (retryAfterHeader) {
const parsed = parseInt(retryAfterHeader, 10)
if (!isNaN(parsed)) {
retryAfter = parsed
} else {
// Try parsing as date
const date = Date.parse(retryAfterHeader)
if (!isNaN(date)) {
retryAfter = Math.max(0, Math.ceil((date - Date.now()) / 1000))
}
}
}
return {
retryAfter,
limit: headers["x-ratelimit-limit"] ? parseInt(headers["x-ratelimit-limit"], 10) : undefined,
remaining: headers["x-ratelimit-remaining"] ? parseInt(headers["x-ratelimit-remaining"], 10) : undefined,
reset: headers["x-ratelimit-reset"] ? parseInt(headers["x-ratelimit-reset"], 10) : undefined,
}
}
// ============================================
// 3. HTTP client with rate limit handling
// ============================================
const makeRateLimitAwareClient = Effect.gen(function* () {
const httpClient = yield* HttpClient.HttpClient
return {
get: <T>(url: string) =>
Effect.gen(function* () {
const response = yield* httpClient.get(url)
if (response.status === 429) {
const rateLimitInfo = parseRateLimitHeaders(response.headers)
yield* Effect.log(
`Rate limited. Retry after ${rateLimitInfo.retryAfter}s`
)
return yield* Effect.fail(new RateLimitedError({
retryAfter: rateLimitInfo.retryAfter,
limit: rateLimitInfo.limit,
remaining: rateLimitInfo.remaining,
reset: rateLimitInfo.reset,
}))
}
return yield* HttpClientResponse.json(response) as Effect.Effect<T>
}).pipe(
Effect.retry({
schedule: Schedule.recurWhile<RateLimitedError>(
(e) => e._tag === "RateLimitedError"
).pipe(
Schedule.intersect(Schedule.recurs(3)),
Schedule.delayed((_, error) =>
Duration.seconds(error.retryAfter + 1) // Add 1s buffer
)
),
while: (error) => error._tag === "RateLimitedError",
})
),
}
})
// ============================================
// 4. Proactive rate limiting (client-side)
// ============================================
interface RateLimiter {
readonly acquire: () => Effect.Effect<void>
readonly release: () => Effect.Effect<void>
}
const makeClientRateLimiter = (requestsPerSecond: number) =>
Effect.gen(function* () {
const tokens = yield* Ref.make(requestsPerSecond)
const interval = 1000 / requestsPerSecond
// Refill tokens periodically
yield* Effect.fork(
Effect.forever(
Effect.gen(function* () {
yield* Effect.sleep(Duration.millis(interval))
yield* Ref.update(tokens, (n) => Math.min(n + 1, requestsPerSecond))
})
)
)
const limiter: RateLimiter = {
acquire: () =>
Effect.gen(function* () {
let acquired = false
while (!acquired) {
const current = yield* Ref.get(tokens)
if (current > 0) {
yield* Ref.update(tokens, (n) => n - 1)
acquired = true
} else {
yield* Effect.sleep(Duration.millis(interval))
}
}
}),
release: () => Ref.update(tokens, (n) => Math.min(n + 1, requestsPerSecond)),
}
return limiter
})
// ============================================
// 5. Combined client
// ============================================
const makeRobustHttpClient = (requestsPerSecond: number) =>
Effect.gen(function* () {
const httpClient = yield* HttpClient.HttpClient
const rateLimiter = yield* makeClientRateLimiter(requestsPerSecond)
return {
get: <T>(url: string) =>
Effect.gen(function* () {
// Wait for rate limiter token
yield* rateLimiter.acquire()
const response = yield* httpClient.get(url)
if (response.status === 429) {
const info = parseRateLimitHeaders(response.headers)
yield* Effect.log(`Server rate limit hit, waiting ${info.retryAfter}s`)
yield* Effect.sleep(Duration.seconds(info.retryAfter))
return yield* Effect.fail(new Error("Rate limited"))
}
return yield* HttpClientResponse.json(response) as Effect.Effect<T>
}).pipe(
Effect.retry(
Schedule.exponential("1 second").pipe(
Schedule.intersect(Schedule.recurs(3))
)
)
),
}
})
// ============================================
// 6. Batch requests to stay under limits
// ============================================
const batchRequests = <T>(
urls: string[],
requestsPerSecond: number
) =>
Effect.gen(function* () {
const httpClient = yield* HttpClient.HttpClient
const results: T[] = []
const interval = 1000 / requestsPerSecond
for (const url of urls) {
const response = yield* httpClient.get(url)
const data = yield* HttpClientResponse.json(response) as Effect.Effect<T>
results.push(data)
// Wait between requests
if (urls.indexOf(url) < urls.length - 1) {
yield* Effect.sleep(Duration.millis(interval))
}
}
return results
})
// ============================================
// 7. Usage
// ============================================
const program = Effect.gen(function* () {
const client = yield* makeRateLimitAwareClient
yield* Effect.log("Making rate-limited request...")
const data = yield* client.get("https://api.example.com/data").pipe(
Effect.catchTag("RateLimitedError", (error) =>
Effect.gen(function* () {
yield* Effect.log(`Gave up after rate limiting. Limit: ${error.limit}`)
return { error: "rate_limited" }
})
)
)
yield* Effect.log(`Result: ${JSON.stringify(data)}`)
})
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 2d ago First seen · 244 lines · 1,598 tokens per session scan A 8018fc208051
handle-rate-limiting-responses is a cursor rule published in the GitHub repository PaulJPhilp/EffectPatterns (796 stars, last pushed 2mo ago), licensed MIT. It adds 1,598 tokens to every session, about $0.0080 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-09-03.
Other cursor rules, from other repositories
typescript-code-generation-rules
Rules for generating TypeScript code in Next.js 14 components, including component definition syntax, props definitions, and named/default exports.
react-and-typescript-general-rules
General rules for React and TypeScript projects, focusing on code clarity and best practices.
typescript-coding-style
Enforces code style and best practices for TypeScript files.
javascript-typescript-code-style
Rules for JavaScript and TypeScript code style, including modern features, functional patterns, and descriptive naming conventions.
code-style-and-improvements
This rule focuses on code style, refactoring suggestions, and leveraging the latest ES and Node.js features for JavaScript, TypeScript, and Python files.
key-conventions
Key coding conventions for Astro projects including style guide and typescript.