# How Holehe Uses TrioProgress for Asynchronous Progress Tracking

> Learn how Holehe uses TrioProgress for asynchronous progress tracking. Monitor coroutine events with this custom Trio instrument for live tqdm progress bar feedback during OSINT scans.

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

---

**TrioProgress is a custom Trio instrument in Holehe that monitors coroutine lifecycle events to drive a real-time tqdm progress bar, giving users live visual feedback during email OSINT scans.**

Holehe is an asynchronous email reconnaissance tool that checks username and email availability across hundreds of platforms. Because it leverages the Trio async library to run concurrent checks, it needs a way to visualize progress without blocking the event loop. The solution is a custom instrument called `TrioProgress`, implemented in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py), that hooks into Trio's task scheduling system to track completion status in real time.

## What is TrioProgress?

`TrioProgress` is a specialized class that inherits from `trio.abc.Instrument`, allowing it to receive low-level callbacks whenever tasks are spawned or completed. According to the source code in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py), this instrument bridges Trio's internal task management with the `tqdm` library to produce a command-line progress bar.

The class maintains three critical state attributes:

- **`_total`**: The total number of modules (check coroutines) to execute
- **`_bar`**: A `tqdm.tqdm` instance configured with the module count
- **`_running`**: A counter tracking currently active tasks

## How TrioProgress Tracks Async Tasks

The instrument works by implementing two key callback methods that Trio automatically invokes during the nursery lifecycle.

### Task Spawn Monitoring

When the main scanning loop starts a new check coroutine, Trio calls `task_spawned()`. This method increments the internal `_running` counter and advances the progress bar by one unit:

```python

# holehe/instruments.py

def task_spawned(self, task):
    self._running += 1
    self._bar.update(1)

```

This callback fires immediately when `nursery.start_soon()` is invoked in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), ensuring the progress bar updates the instant a new platform check begins.

### Task Exit Handling

When a coroutine completes—whether successfully or with an exception—Trio invokes `task_exited()`. The instrument decrements the running counter and closes the progress bar when all tasks finish:

```python

# holehe/instruments.py

def task_exited(self, task):
    self._running -= 1
    if self._running == 0:
        self._bar.close()

```

This ensures the visual indicator accurately reflects the actual state of the async worker pool, preventing the bar from stalling on failed or cancelled checks.

### tqdm Integration

The constructor initializes the visual component using tqdm's standard API:

```python

# holehe/instruments.py

def __init__(self, total):
    self._total = total
    self._bar = tqdm(total=total, desc="Scanning", unit="module")
    self._running = 0

```

By setting `unit="module"`, the progress bar displays "module" as the unit of work, making it clear to users that each increment represents one platform check completing.

## Integration in the Core Scanning Loop

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the main async runner orchestrates the instrument's lifecycle around the Trio nursery. Before spawning any check coroutines, the code registers the instrument using Trio's low-level API. After all tasks complete, it cleans up by removing the instrument.

```python

# holehe/core.py (conceptual excerpt)

import trio
from .instruments import TrioProgress

async def maincore(module_coroutines):
    # Initialize progress tracking with total module count

    instrument = TrioProgress(total=len(module_coroutines))
    
    # Register the instrument to receive task callbacks

    trio.lowlevel.add_instrument(instrument)
    
    try:
        async with trio.open_nursery() as nursery:
            for coro in module_coroutines:
                nursery.start_soon(coro)
    finally:
        # Ensure cleanup happens even if errors occur

        trio.lowlevel.remove_instrument(instrument)

```

This pattern ensures the progress bar exists only during active scanning and does not interfere with other Trio operations or subsequent runs.

## Practical Usage Examples

### Command Line Execution

When running Holehe normally, the TrioProgress bar appears automatically as modules execute:

```bash
holehe -u user@example.com

```

Output shows real-time progress:

```

Scanning: 23/45 [===============>................]  51%

```

### Custom Script Integration

You can reuse the same progress tracking in custom async scripts that leverage Holehe's checking modules:

```python
import trio
from holehe.instruments import TrioProgress
from holehe.modules import my_custom_check

async def run_custom_scan(email):
    # Build list of coroutines

    coroutines = [
        my_custom_check(email),
        # ... additional checks

    ]
    
    # Setup progress tracking

    progress = TrioProgress(total=len(coroutines))
    trio.lowlevel.add_instrument(progress)
    
    try:
        async with trio.open_nursery() as nursery:
            for coro in coroutines:
                nursery.start_soon(coro)
    finally:
        trio.lowlevel.remove_instrument(progress)

if __name__ == "__main__":
    trio.run(run_custom_scan, "target@example.com")

```

### Programmatic Progress Monitoring

If you need to extract progress data for a GUI or logging system instead of stdout, subclass `TrioProgress` to expose the counters:

```python
from holehe.instruments import TrioProgress

class LoggingProgress(TrioProgress):
    def task_spawned(self, task):
        super().task_spawned(task)
        logger.info(f"Started {task.name}, {self._running} active")
    
    def task_exited(self, task):
        super().task_exited(task)
        logger.info(f"Finished {task.name}, {self._running} remaining")

```

## Summary

- **TrioProgress** is defined in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) and inherits from `trio.abc.Instrument` to receive async task callbacks.
- It tracks coroutine lifecycle through `task_spawned()` and `task_exited()` methods, maintaining an internal counter of active checks.
- The instrument wraps a `tqdm` progress bar that updates in real-time as each platform check starts, providing "Scanning: X/total" visual feedback.
- In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the instrument is registered with `trio.lowlevel.add_instrument()` before opening the nursery and removed with `remove_instrument()` after completion.
- This architecture separates progress visualization from business logic while leveraging Trio's native instrumentation API for zero-overhead monitoring.

## Frequently Asked Questions

### How does TrioProgress handle failed or crashing modules?

When a check coroutine raises an exception, Trio's nursery management still invokes `task_exited()` on the instrument. The method decrements the `_running` counter regardless of exit status, ensuring the progress bar advances even when modules crash. The exception propagation is handled separately by the nursery's error policy, but the visual indicator remains accurate.

### Can I use TrioProgress with asyncio instead of Trio?

No. `TrioProgress` specifically implements `trio.abc.Instrument`, which is part of Trio's low-level instrumentation API. This protocol has no equivalent in asyncio's architecture. To use Holehe's progress tracking, you must run the checks within Trio's event loop using `trio.run()` or `trio.lowlevel.start_guest_run()`.

### Why does the progress bar show "module" as the unit instead of "site" or "check"?

The `__init__` method in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) explicitly sets `unit="module"` when constructing the tqdm instance. This terminology reflects the codebase architecture where each platform checker is implemented as a separate module. You can customize this by subclassing and overriding the constructor, but the default aligns with Holehe's internal naming conventions.

### Is the progress bar thread-safe when running multiple scans concurrently?

Yes, because Trio instruments are bound to a specific Trio thread and event loop. Each call to `trio.lowlevel.add_instrument()` registers the instance only for that particular task tree. If you run multiple scans in parallel using separate Trio low-level calls or guest runs, each maintains its own instrument state, preventing race conditions on the tqdm output.