# Handling Connection Errors and Timeouts Across Different Website Modules in Holehe

> Learn how Holehe robustly handles connection errors and timeouts across its modules. Discover its strategy for graceful degradation without crashing scans.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-08-20

---

**Holehe handles connection errors and timeouts by configuring a global timeout in the core CLI, applying per-request timeouts in individual modules, and wrapping all HTTP calls in `try/except httpx.HTTPError` blocks to ensure graceful degradation without crashing the entire scan.**

Holehe is an open-source OSINT tool that checks whether an email address is registered across hundreds of websites. Because it interacts with diverse third-party services, **handling connection errors and timeouts across different website modules** is critical to maintaining reliable operation. This article examines how the `megadose/holehe` repository implements consistent error handling from the CLI layer down to individual site modules.

## Global Timeout Configuration in Core.py

The foundation of Holehe's timeout strategy resides in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), where command-line arguments are parsed and the shared HTTP client is instantiated.

The CLI exposes a `--timeout` flag with a default value of 10 seconds:

```python

# Lines 194-213 in holehe/core.py

parser.add_argument("--timeout", type=int, default=10, help="Timeout for each request")
args = parser.parse_args()

# The timeout value is passed to httpx.AsyncClient

client = httpx.AsyncClient(timeout=args.timeout)

```

This `httpx.AsyncClient` is created once and injected into all module functions. The **global timeout** establishes a ceiling for all network operations, preventing the entire scan from hanging indefinitely when services become unresponsive.

## Per-Module Request Patterns and Exception Handling

Each website module in `holehe/modules/` receives the shared client and implements its own request logic with **per-call timeout overrides** and granular error handling. This two-level timeout system allows modules to specify shorter timeouts for known-fast services while respecting the global maximum.

### Standard Error Handling Pattern

The consistent pattern across modules follows this structure:

1. **Attempt the HTTP request** with an explicit timeout parameter
2. **Catch `httpx.HTTPError`** (the base class for all httpx exceptions)
3. **Return a normalized result dictionary** with `rateLimit: False` and `exists: False`

This pattern appears in modules such as [`social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/social_media/twitter.py), [`forum/onlinesequencer.py`](https://github.com/megadose/holehe/blob/main/forum/onlinesequencer.py), and [`medical/caringbridge.py`](https://github.com/megadose/holehe/blob/main/medical/caringbridge.py).

### Example: Simple GET with Timeout and Error Handling

Below is a representative implementation based on the Twitter module pattern:

```python

# Pattern found in holehe/modules/social_media/twitter.py

import httpx

async def twitter(email: str, client: httpx.AsyncClient, out: list):
    try:
        r = await client.get(
            "https://twitter.com/users/email_available.json",
            params={"email": email},
            timeout=5,  # Per-request override of global timeout

        )
        data = r.json()
        out.append(
            {
                "name": "twitter",
                "rateLimit": False,
                "exists": data.get("taken", False),
                "emailrecovery": None,
                "phoneNumber": None,
                "others": None,
            }
        )
    except httpx.HTTPError:
        # Catches ConnectTimeout, ReadTimeout, ConnectError, etc.

        out.append(
            {
                "name": "twitter",
                "rateLimit": False,
                "exists": False,
                "emailrecovery": None,
                "phoneNumber": None,
                "others": None,
            }
        )

```

The **5-second per-request timeout** allows Twitter checks to fail fast if the service stalls, while the `except httpx.HTTPError` block ensures any transport-level failure produces a valid result rather than an unhandled exception.

### Example: POST with Rate-Limit Detection

Some modules extend the pattern to detect rate limiting by inspecting HTTP status codes before falling back to generic error handling:

```python

# Pattern found in holehe/modules/medical/caringbridge.py

async def caringbridge(email: str, client: httpx.AsyncClient, out: list):
    try:
        r = await client.post(
            "https://www.caringbridge.org/signin",
            data={"email": email},
            timeout=3,
        )
        if r.status_code == 429:
            # Explicit rate-limit detection

            out.append(
                {
                    "name": "caringbridge",
                    "rateLimit": True,
                    "exists": False,
                    "emailrecovery": None,
                    "phoneNumber": None,
                    "others": None,
                }
            )
            return
        # Normal response processing...

        
    except httpx.HTTPError:
        # Generic network failure handling

        out.append(
            {
                "name": "caringbridge",
                "rateLimit": False,
                "exists": False,
                "emailrecovery": None,
                "phoneNumber": None,
                "others": None,
            }
        )

```

This demonstrates how modules can **distinguish rate limiting from connection failures** while maintaining the same exception-handling foundation.

## Exception Hierarchy and Specific Error Types

Holehe leverages httpx's structured exception hierarchy for precise error classification:

| Exception | Typical Cause | Handling in Modules |
|-----------|-------------|---------------------|
| `httpx.ConnectTimeout` | TCP connection cannot be established within timeout | Caught by `httpx.HTTPError` base class |
| `httpx.ReadTimeout` | Server connected but response not received in time | Caught by `httpx.HTTPError` base class |
| `httpx.ConnectError` | DNS failure, refused connection, or network unreachable | Caught by `httpx.HTTPError` base class |
| `httpx.HTTPStatusError` | 4xx/5xx response (when using `raise_for_status()`) | Checked via `r.status_code` before raising |

By catching `httpx.HTTPError` rather than individual subclasses, modules achieve **comprehensive coverage** of transport failures without verbose exception lists. When more granular handling is needed (such as the 429 rate-limit check), modules inspect response attributes before the exception propagates.

## Result Normalization and Caller Contract

A critical aspect of Holehe's error handling is the **guaranteed return format**. Every module function populates a result list with dictionaries containing these standardized keys:

- `name`: The service identifier
- `rateLimit`: Boolean indicating throttling detection
- `exists`: Boolean indicating account existence
- `emailrecovery`: Recovery email if exposed
- `phoneNumber`: Phone number if exposed
- `others`: Additional data

This contract ensures that the aggregation logic in [`holehe/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/__init__.py) can process results uniformly, regardless of whether the underlying request succeeded, timed out, or encountered a connection error. The **graceful degradation** approach means a single failing service never corrupts the entire scan output.

## Timeout Strategy Benefits

The layered timeout implementation in Holehe provides several operational advantages:

**Predictable execution time**: The global timeout bounds total scan duration, while per-request timeouts prevent individual slow services from dominating execution.

**Resilience to network variability**: By catching all `httpx.HTTPError` subclasses, modules handle transient failures (packet loss, brief outages) without manual retry logic.

**Clear operational signals**: The `rateLimit` flag differentiates temporary service restrictions from definitive "account not found" responses, enabling intelligent retry strategies at the application level.

## Summary

- **Global timeout** is configured via CLI in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) and applied through `httpx.AsyncClient(timeout=...)`
- **Per-module timeouts** override the global value for services with known latency characteristics
- **Exception handling** uses `try/except httpx.HTTPError` to catch all transport failures including `ConnectTimeout`, `ReadTimeout`, and `ConnectError`
- **Normalized results** ensure every module returns a consistent dictionary structure, preventing cascading failures
- **Rate-limit detection** extends the base pattern by inspecting HTTP status codes before exception handling

## Frequently Asked Questions

### What timeout values does Holehe use by default?

Holehe defaults to a **10-second global timeout** for all HTTP requests. Individual modules often specify shorter per-request timeouts—commonly 3 to 5 seconds—for services expected to respond quickly. Users can override the global default using the `--timeout` CLI argument defined in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).

### Why does Holehe catch `httpx.HTTPError` instead of specific exception types?

Catching the **base `httpx.HTTPError` class** provides comprehensive protection against all transport-level failures without requiring modules to enumerate every possible exception. This includes `ConnectTimeout`, `ReadTimeout`, `ConnectError`, and other network problems. Modules only need more specific handling when detecting application-level signals like HTTP 429 rate-limit responses.

### How does Holehe prevent one slow website from stalling the entire scan?

Holehe uses **asynchronous execution with individual timeouts** combined with **graceful exception handling**. Each module runs independently with its own timeout constraints. When a request fails or times out, the module returns a normalized "unknown" result rather than propagating an exception, allowing the aggregation layer to continue processing other services uninterrupted.

### Can modules distinguish between rate limiting and server unavailability?

Yes. While the base pattern catches all `httpx.HTTPError` exceptions as generic failures, modules can inspect HTTP response attributes **before** an exception occurs. By checking `r.status_code == 429` or similar indicators, modules set `rateLimit: True` in the result dictionary to signal throttling conditions separately from connection timeouts or transport errors.