# How Progress Tracking Works in Holehe with TrioProgress

> Discover how Holehe uses the TrioProgress class to integrate Trio instruments and automatically update a tqdm progress bar as module-checking tasks complete.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-30

---

**Holehe implements progress tracking via a custom `TrioProgress` class that acts as a Trio instrument, automatically updating a `tqdm` progress bar every time a module-checking task completes.**

Asynchronous OSINT tool [Holehe](https://github.com/megadose/holehe) checks account existence across hundreds of websites concurrently. To keep users informed during long-running scans, its developers built a lightweight progress tracking system that hooks into Trio's low-level instrumentation API rather than cluttering module code with manual updates.

## The TrioProgress Instrument Architecture

Holehe's progress tracking centers on the `TrioProgress` class in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py). This class implements `trio.abc.Instrument`, Trio's official interface for observing task lifecycle events.

```python

# holehe/instruments.py (simplified)

import trio
from tqdm import tqdm

class TrioProgress(trio.abc.Instrument):
    def __init__(self, total):
        self.tqdm = tqdm(total=total)

    def task_exited(self, task):
        # Filter for module-specific tasks

        if task.name.split(".")[-1] == "launch_module":
            self.tqdm.update(1)

```

The `task_exited` hook fires automatically whenever any Trio task finishes. By checking that the task name ends with `"launch_module"`, the instrument distinguishes actual website-checking coroutines from internal Trio tasks, ensuring accurate progress counting.

## Registering and Running the Instrument

Progress tracking activation happens in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) within the `maincore()` function. Here's the complete lifecycle:

```python

# holehe/core.py (key sections)

from holehe.instruments import TrioProgress

async def maincore():
    websites = [...]  # List of modules to run

    
    # 1. Initialize progress bar with total module count

    instrument = TrioProgress(len(websites))
    
    # 2. Register with Trio runtime

    trio.lowlevel.add_instrument(instrument)
    
    # 3. Launch all module checks concurrently

    async with trio.open_nursery() as nursery:
        for website in websites:
            nursery.start_soon(launch_module, website, email, client, out)
    
    # 4. Clean up instrument when done

    trio.lowlevel.remove_instrument(instrument)

```

The instrument remains active throughout the nursery's execution, receiving callbacks for every task exit. No `try/finally` or manual step counting occurs inside `launch_module`—progress updates happen transparently through Trio's instrumentation layer.

## Why This Design Pattern Matters

Decoupling progress tracking from business logic offers three concrete advantages in Holehe's codebase:

- **Zero module modifications** — Website checkers in `holehe/modules/` need no awareness of UI concerns
- **Automatic accuracy** — The instrument counts actual task completions, not scheduled starts or timeouts
- **Clean teardown** — `remove_instrument()` guarantees the progress bar stops receiving updates before result processing begins

This pattern scales naturally: adding new websites to check automatically extends the progress bar's total without code changes.

## Complete Working Example

To observe the TrioProgress pattern in isolation:

```python
import trio
from tqdm import tqdm

class DemoProgress(trio.abc.Instrument):
    def __init__(self, total):
        self.tqdm = tqdm(total=total, desc="Processing")
    
    def task_exited(self, task):
        if "worker" in task.name:
            self.tqdm.update(1)

async def worker(n, delay):
    await trio.sleep(delay)
    return n * 2

async def main():
    tasks = [(i, 0.1 * i) for i in range(1, 6)]
    instrument = DemoProgress(len(tasks))
    
    trio.lowlevel.add_instrument(instrument)
    
    async with trio.open_nursery() as nursery:
        for n, delay in tasks:
            nursery.start_soon(worker, n, delay, name=f"worker_{n}")
    
    trio.lowlevel.remove_instrument(instrument)
    instrument.tqdm.close()

trio.run(main)

```

This produces output matching Holehe's behavior:

```

Processing:  60%|████████████        | 3/5 [00:00<00:00,  9.87it/s]

```

## Summary

- **`TrioProgress`** in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) implements `trio.abc.Instrument` to observe task exits
- **Registration** via `trio.lowlevel.add_instrument()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) enables automatic callbacks
- **Filtering** by task name (`"launch_module"`) ensures only relevant coroutines increment progress
- **Decoupled design** keeps progress logic separate from website-checking modules
- **Clean lifecycle** with explicit `remove_instrument()` prevents ghost updates

## Frequently Asked Questions

### What is a Trio instrument?

A Trio instrument is any class implementing `trio.abc.Instrument` that receives callbacks for task scheduling, entry, and exit events. Instruments observe the runtime without modifying task behavior, making them ideal for logging, profiling, and progress tracking as used in Holehe.

### Why does TrioProgress check the task name?

Task name filtering prevents counting internal Trio tasks like nursery management or system callbacks. Holehe names all module-running tasks with `"launch_module"` suffix, so `task.name.split(".")[-1] == "launch_module"` identifies genuine website checks accurately.

### Can I use TrioProgress with other async libraries?

No—`trio.abc.Instrument` is Trio-specific. Libraries using `asyncio` would need equivalent instrumentation via `asyncio.Task` callbacks or context variables. Holehe's strict Trio dependency makes instruments the optimal choice.

### Where does the tqdm progress bar appear?

The progress bar renders in the terminal via standard error stream. When running Holehe with `holehe -T 15 user@example.com`, the bar displays above module results and clears automatically on completion, matching typical CLI tool behavior.