add-timeouts-to-http-requests

A coding rule that requires HTTP requests to have a time limit. HTTP requests are calls from one application to another over the web.

In plain words
What is it for?
Use it when writing TypeScript HTTP clients to add timeouts and optionally return a custom timeout error.
Why use it?
It prevents a request from waiting indefinitely when a remote service is slow or unreachable.

Cursor rule

Install

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.

agentmods
npx agentmods add rules/pauljphilp/effectpatterns/add-timeouts-to-http-requests
Clone the repo
git clone --depth 1 https://github.com/PaulJPhilp/EffectPatterns
Per session 1,112 This file is loaded in full into every session.
When invoked 1,112 The same file — it is already loaded in full.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

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.

ModelPer sessionOnce invoked
Fable 5 $0.01112 $0.01112
Opus 5 $0.00556 $0.00556
Sonnet 5 $0.00222 $0.00222
Haiku 4.5 $0.00111 $0.00111

Measured yesterday against content hash 94118e5dedf4, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

add-timeouts-to-http-requests 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 yesterday.

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.

content/published/rules/cursor/add-timeouts-to-http-requests.mdc · 190 lines

How it starts

The opening of the file, as written. The whole thing — 190 lines — stays where its author put it; the contents beside it link to each section on GitHub.

description: Always set timeouts on HTTP requests to ensure your application doesn't hang. globs: "**/*.ts" alwaysApply: true

Add Timeouts to HTTP Requests

Rule: Always set timeouts on HTTP requests to ensure your application doesn't hang.

Example

import { Effect, Duration, Data } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform"

// ============================================
// 1. Basic request timeout
// ============================================

const fetchWithTimeout = (url: string, timeout: Duration.DurationInput) =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient

    return yield* client.get(url).pipe(
      Effect.flatMap((r) => HttpClientResponse.json(r)),
      Effect.timeout(timeout)
    )
    // Returns Option<A> - None if timed out
  })

// ============================================
// 2. Timeout with custom error
// ============================================

class RequestTimeoutError extends Data.TaggedError("RequestTimeoutError")<{
  readonly url: string
  readonly timeout: Duration.Duration
}> {}

const fetchWithTimeoutError = (url: string, timeout: Duration.DurationInput) =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient

    return yield* client.get(url).pipe(
      Effect.flatMap((r) => HttpClientResponse.json(r)),
      Effect.timeoutFail({
        duration: timeout,
        onTimeout: () => new RequestTimeoutError({
          url,
          timeout: Duration.decode(timeout),
        }),
      })
    )
  })

// ============================================
// 3. Different timeouts for different phases
// ============================================

const fetchWithPhasedTimeouts = (url: string) =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient

    // Connection timeout (initial)
    const response = yield* client.get(url).pipe(
      Effect.timeout("5 seconds"),
      Effect.flatten,
      Effect.mapError(() => new Error("Connection timeout"))
    )

    // Read timeout (body)
    const body = yield* HttpClientResponse.text(response).pipe(
      Effect.timeout("30 seconds"),
      Effect.flatten,
      Effect.mapError(() => new Error("Read timeout"))
    )

    return body
  })

// ============================================
// 4. Timeout with fallback
// ============================================

interface ApiResponse {
  data: unknown
  cached: boolean
}

const fetchWithFallback = (url: string): Effect.Effect<ApiResponse> =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient

    return yield* client.get(url).pipe(
      Effect.flatMap((r) => HttpClientResponse.json(r)),
      Effect.map((data) => ({ data, cached: false })),
      Effect.timeout("5 seconds"),
      Effect.flatMap((result) =>
        result._tag === "Some"
          ? Effect.succeed(result.value)
          : Effect.succeed({ data: null, cached: true })  // Fallback
      )
    )
  })

// ============================================
// 5. Timeout with interrupt
// ============================================

const fetchWithInterrupt = (url: string) =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient

    return yield* client.get(url).pipe(
      Effect.flatMap((r) => HttpClientResponse.json(r)),
      Effect.interruptible,
      Effect.timeout("10 seconds")
    )
    // Fiber is interrupted if timeout, freeing resources
  })

// ============================================
// 6. Configurable timeout wrapper
// ============================================

interface TimeoutConfig {
  readonly connect: Duration.DurationInput
  readonly read: Duration.DurationInput
  readonly total: Duration.DurationInput
}

const defaultTimeouts: TimeoutConfig = {
  connect: "5 seconds",
  read: "30 seconds",
  total: "60 seconds",
}

const createHttpClient = (config: TimeoutConfig = defaultTimeouts) =>
  Effect.gen(function* () {
    const baseClient = yield* HttpClient.HttpClient

    return {
      get: (url: string) =>
        baseClient.get(url).pipe(
          Effect.timeout(config.connect),
          Effect.flatten,
          Effect.flatMap((r) =>
            HttpClientResponse.json(r).pipe(
              Effect.timeout(config.read),
              Effect.flatten
            )
          ),
          Effect.timeout(config.total),
          Effect.flatten
        ),
    }
  })

// ============================================
// 7. Usage
// ============================================

const program = Effect.gen(function* () {
  yield* Effect.log("Fetching with timeout...")

  const result = yield* fetchWithTimeoutError(
    "https://api.example.com/slow",
    "5 seconds"
  ).pipe(
    Effect.catchTag("RequestTimeoutError", (error) =>
      Effect.gen(function* () {
        yield* Effect.log(`Request to ${error.url} timed out`)
        return { error: "timeout" }
      })
    )
  )

  yield* Effect.log(`Result: ${JSON.stringify(result)}`)
})

Read the full file on GitHub · 190 lines

Changes

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.

  1. yesterday First seen · 190 lines · 1,112 tokens per session scan A 94118e5dedf4

Subscribe to this mod's changes

add-timeouts-to-http-requests is a cursor rule published in the GitHub repository PaulJPhilp/EffectPatterns (794 stars, last pushed 2mo ago), licensed MIT. It adds 1,112 tokens to every session, about $0.0056 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-08-30.