How holehe Handles Errors in Individual Modules: A Deep Dive into Two-Layer Exception Management

holehe catches errors at both the module and core levels, converting all exceptions into standardized dictionary records that let the scan continue uninterrupted.

The holehe tool, developed by @megadose, checks email addresses against dozens of online services through async Python modules. Since any single service can fail without invalidating the entire search, the codebase implements a robust two-layer error handling strategy that guarantees every module contributes a usable result—whether successful or not.

The Two-Layer Error Handling Architecture

holehe organizes error handling into distinct module-level and core-level responsibilities. This ensures graceful degradation when services rate-limit requests or experience outages.

Module-Level: Service-Specific Exception Catching

Each service module wraps its HTTP logic in a try … except Exception: block. This gives module authors control over interpreting specific response patterns—distinguishing, for example, between a genuine "email exists" response and a throttling rejection.

In holehe/modules/social_media/twitter.py (lines 30-36), the Twitter module implements this pattern:


# holehe/modules/social_media/twitter.py

async def twitter(email, client, out):
    name, domain, method = "twitter", "twitter.com", "register"
    try:
        req = await client.get("https://api.twitter.com/i/users/email_available.json",
                               params={"email": email})
        # … normal success handling …

    except Exception:                     # ← module-level catch

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": False,
            "rateLimit": True,            # service signaled throttling

            "error": True,                # we hit an exception

            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

The module author chooses to set rateLimit=True because most Twitter failures stem from request throttling. This semantic context would be lost at a generic handler.

Core-Level: Universal Safety Net

The launch_module function in holehe/core.py (lines 66-78) provides a fallback wrapper. It catches any exception that escapes the module's own handling—whether from coding errors, unexpected API changes, or network interruptions:


# holehe/core.py – generic launcher

async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)   # run the service-specific coroutine

    except Exception:                     # ← core-level safety net

        name = str(module).split('<function ')[1].split(' ')[0]
        out.append({
            "name": name,
            "domain": data[name],
            "rateLimit": True,
            "error": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

This guarantees the out list receives a properly formed entry even for completely broken modules, preventing the entire scan from crashing.

The Standardized Error Record Format

Both layers produce identical dictionary structures, enabling uniform downstream processing. Every result contains these keys:

Key Purpose Typical Error Value
name Service identifier "twitter"
domain Service domain "twitter.com"
method Lookup technique "register" or omitted
frequent_rate_limit Persistent throttling flag False
rateLimit Request was throttled True
error Exception was caught True
exists Email confirmed on service False (unknown due to error)
emailrecovery Recovery email leaked None
phoneNumber Phone leaked None
others Miscellaneous data None

The exists=False field is critical: it prevents error states from being misinterpreted as positive matches. Meanwhile, error=True triggers red [!] markers in the CLI output.

Visual Feedback in the Command Line Interface

When you execute holehe, the standardized records render as intuitive status indicators:

$ holehe example@example.com
[!] twitter      # ← rate-limit or exception caught (error=True)

[-] instagram    # ← email not found on service

[+] github       # ← email found (exists=True)

...

The print_result function in core.py consumes these dictionaries without needing service-specific logic—direct proof of how the uniform schema simplifies the architecture.

Design Rationale: Why Two Layers?

Module-level handling preserves semantic nuance. A developer can inspect HTTP status codes, JSON error fields, or response headers to classify failures precisely.

Core-level handling provides defense in depth. Even if a module lacks proper exception wrapping—perhaps a new contributor's first submission—the orchestration layer prevents cascade failures.

This separation follows the fail-fast, fail-safe principle: modules should handle expected failures explicitly, but the system survives unexpected ones regardless.

Summary

  • holehe handles errors in individual modules through a two-layer architecture: service-specific catching plus a universal core wrapper.
  • All errors convert to standardized dictionaries with error=True, rateLimit=True, and exists=False, ensuring consistent downstream processing.
  • Module-level handlers in files like holehe/modules/social_media/twitter.py preserve contextual interpretation of API responses.
  • The core launcher at holehe/core.py lines 66-78 guarantees scan continuity even against completely unhandled exceptions.
  • Uniform dictionary schemas eliminate special-case logic in the UI layer, as implemented in print_result.

Frequently Asked Questions

What happens if a holehe module forgets to wrap its HTTP request in try/except?

The core-level launch_module function in holehe/core.py catches the escaping exception and produces a standard error record anyway. The scan continues normally, displaying [!] for that service.

Why does almost every holehe error set rateLimit=True?

Most module authors treat exceptions as throttling indicators because rate-limiting is the dominant failure mode for aggressive email enumeration. The core launcher follows this convention for consistency.

Can I customize error handling for a specific service module?

Yes. The module-level try … except block in each service file allows custom logic—for instance, parsing specific error codes from the target API before falling back to the generic error record format.

Where does holehe display the error status to users?

The print_result function in holehe/core.py renders dictionary records as CLI symbols: [!] for errors, [-] for confirmed non-existence, and [+] for successful email matches.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →