Require Timeouts on Python Network Calls

Catches requests calls without a timeout, disabled httpx timeouts, and boto3, Redis, and database clients left on long or unbounded defaults.

What it flags

Flaggedwebhooks/delivery.py
import json

import boto3
import requests

from webhooks.signing import sign

s3 = boto3.client("s3")


def deliver_webhook(endpoint_url: str, secret: str, event: dict) -> None:
    body = json.dumps(event)
    s3.put_object(Bucket="webhook-archive", Key=f"{event['id']}.json", Body=body)
    # No timeout: a receiver that never responds blocks this worker indefinitely
    response = requests.post(
        endpoint_url,
        data=body,
        headers={"Content-Type": "application/json", "X-Signature": sign(secret, body)},
    )
    response.raise_for_status()
Passeswebhooks/delivery.py
import json

import boto3
import requests
from botocore.config import Config

from webhooks.signing import sign

S3_CONFIG = Config(connect_timeout=2, read_timeout=5, retries={"max_attempts": 3})
s3 = boto3.client("s3", config=S3_CONFIG)
WEBHOOK_TIMEOUT = (3.05, 10)  # (connect, read) in seconds


def deliver_webhook(endpoint_url: str, secret: str, event: dict) -> None:
    body = json.dumps(event)
    s3.put_object(Bucket="webhook-archive", Key=f"{event['id']}.json", Body=body)
    response = requests.post(
        endpoint_url,
        data=body,
        headers={"Content-Type": "application/json", "X-Signature": sign(secret, body)},
        timeout=WEBHOOK_TIMEOUT,
    )
    response.raise_for_status()

Why it matters

The requests library has no default timeout, so one receiver that accepts a connection and never answers pins a worker until the process restarts. When enough workers hang, queues back up and health checks fail even though nothing crashed. A requests timeout bounds connecting and each socket read, not the whole download, and SDK retries multiply every per-attempt limit, so both need deliberate values on production paths.

Agent instructions

cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.

AGENTS.md

Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.

## Require Timeouts on Python Network Calls

Applies to `**/*.py`.

Review Python code on production paths that calls HTTP APIs, cloud SDKs, databases, caches, or other network services for calls that can wait without a bound.

Flag changes that:
- Call `requests.get`, `requests.post`, `requests.request`, or a `requests.Session` method without a `timeout` argument; requests has no default timeout.
- Pass `timeout=None` to a request, or build an `httpx.Client` or `httpx.AsyncClient` with `timeout=None`, which disables the limit.
- Call `urllib.request.urlopen`, `smtplib.SMTP`, `ftplib.FTP`, or `socket.create_connection` without a `timeout`, which uses the global socket default of no timeout unless the application calls `socket.setdefaulttimeout()`.
- Create a `boto3` client or resource for request handlers or workers without a `botocore.config.Config` that sets `connect_timeout`, `read_timeout`, and `retries`; the timeouts default to 60 seconds per attempt.
- Open PostgreSQL, MySQL, or Redis connections without connect and query timeouts, such as a libpq `connect_timeout` and server-side `statement_timeout`, or redis-py `socket_timeout` and `socket_connect_timeout`.
- Retry network calls in a loop with no cap on attempts or total time.
- Await network work that has no timeout of its own without `asyncio.timeout()` or `asyncio.wait_for()`.

Do not flag:
- Calls through a shared session, client factory, or wrapper that already sets timeouts.
- Scripts, notebooks, one-off management commands, and tests with mocked transports.
- Streaming downloads, websockets, or long-poll calls that set an explicit, deliberately long read timeout.

Checks files matching **/*.py.

More Python agents

See all Python code review standards

Run this agent on every pull request

cubic reviews every pull request against the agents you turn on and comments on the lines that break them.

Catch this in your code