# How to Gracefully Stop or Interrupt a Running Strix Security Scan

> Learn how to gracefully stop or interrupt a running Strix security scan using simple keyboard shortcuts, process signals, or programmatic flags for a clean shutdown.

- Repository: [Strix/strix](https://github.com/usestrix/strix)
- Tags: how-to-guide
- Published: 2026-03-26

---

**To gracefully stop a Strix security scan, press Ctrl+C or Ctrl+Q in the TUI, send SIGINT/SIGTERM to the process, or programmatically set the `_scan_stop_event` flag to signal the background thread to exit cleanly.**

Strix implements a robust cancellation mechanism that allows security scans to stop without corrupting data or leaving zombie threads. According to the usestrix/strix source code, the application manages scan lifecycle through a **`threading.Event`** object that coordinates between the UI, signal handlers, and the background scan thread. This architecture ensures resources are released properly regardless of whether the stop request comes from keyboard input, OS signals, or internal API calls.

## Keyboard Shortcuts and Signal Handlers

The Strix TUI provides multiple entry points for requesting a shutdown. In [`strix/interface/tui.py`](https://github.com/usestrix/strix/blob/main/strix/interface/tui.py), the `StrixTUIApp` class defines key bindings at lines 694–698 that map **Ctrl+C** to `action_request_quit` and **Ctrl+Q** to `action_custom_quit`. Both paths ultimately invoke the same graceful shutdown sequence.

For headless or scripted deployments, Strix registers custom signal handlers in `_setup_cleanup_handlers` (lines 61–76). These handlers capture **SIGINT** and **SIGTERM**, invoking `self.tracer.cleanup()` before terminating the process. Sending `kill -2 <pid>` from another terminal triggers the same cleanup path as pressing Ctrl+C.

## The Stop Event Mechanism

At the core of the shutdown logic is a private **`_scan_stop_event`** (a `threading.Event` instance). When the scan starts in `_start_scan_thread` (lines 93–105), the code wraps the heavy `agent.execute_scan` work inside a nested `scan_target` function. Before entering the asyncio loop, the thread checks `self._scan_stop_event.is_set()`. If the flag is set, the scan is skipped entirely, allowing the thread to proceed directly to the `finally` block.

```python
def _start_scan_thread(self) -> None:
    def scan_target() -> None:
        ...
        if not self._scan_stop_event.is_set():
            loop.run_until_complete(agent.execute_scan(self.scan_config))
        ...
        finally:
            loop.close()
            self._scan_completed.set()

```

The `finally` block guarantees that `self._scan_completed.set()` always runs, ensuring the UI can distinguish between a running scan and a finished one, even when interrupted.

## Step-by-Step Graceful Shutdown

When a user initiates a stop, the following sequence executes:

1.  **Trigger Detection** – The TUI receives the keypress or the OS delivers SIGINT/SIGTERM to the process.
2.  **`action_custom_quit` Execution** – Located at lines 55–63 in [`strix/interface/tui.py`](https://github.com/usestrix/strix/blob/main/strix/interface/tui.py), this method sets the stop event and waits for the worker thread:

    ```python
    if self._scan_thread and self._scan_thread.is_alive():
        self._scan_stop_event.set()
        self._scan_thread.join(timeout=1.0)
    self.tracer.cleanup()
    self.exit()
    ```

3.  **Thread Termination** – The background thread detects the set event, skips `execute_scan`, closes its asyncio loop, and signals completion.
4.  **Resource Cleanup** – The tracer flushes telemetry data via `tracer.cleanup()`, and the TUI exits cleanly.

## Implementation Examples

### Interactive Terminal Stop

While the scan progress is visible in the terminal, press **Ctrl+C** or **Ctrl+Q**. The TUI will display the quit dialog, set the stop event, and return to the shell within one second.

### Signal-Based Termination

For a headless Strix process running with PID `1234`, send SIGINT from another shell:

```bash
kill -2 1234

```

The custom handler registered in `_setup_cleanup_handlers` will invoke the same cleanup sequence as the interactive method.

### Programmatic Stop via Python API

Internal tooling can request a stop by directly manipulating the event flag:

```python
from strix.interface.tui import StrixTUIApp

app = StrixTUIApp(args)

# ... after scan has started ...

app._scan_stop_event.set()

```

Direct manipulation of `_scan_stop_event` is intended for internal use; external callers should prefer UI shortcuts or OS signals to avoid thread-safety issues.

## Summary

-   Strix uses a **`threading.Event`** (`_scan_stop_event`) as a cancellation token between the UI and the scan worker thread.
-   **Ctrl+C**, **Ctrl+Q**, **SIGINT**, and **SIGTERM** all trigger the same graceful shutdown path via `action_custom_quit`.
-   The scan thread checks the event before running `execute_scan`, ensuring it exits quickly without leaving orphaned asyncio loops.
-   A **1.0-second timeout** on `thread.join()` prevents the UI from hanging if the thread is unresponsive, while `tracer.cleanup()` ensures telemetry data is flushed.

## Frequently Asked Questions

### What happens if I force-kill Strix with `kill -9`?

Sending SIGKILL (signal 9) terminates the process immediately without allowing the signal handlers in `_setup_cleanup_handlers` to run. This bypasses `tracer.cleanup()` and may result in incomplete telemetry logs or temporary file artifacts, though the scan itself will stop instantly.

### Can I resume a scan after gracefully stopping it?

No. The `_start_scan_thread` method sets `_scan_completed` in its `finally` block, marking the scan lifecycle as finished. Strix does not persist intermediate scan state for resumption; you must restart the scan from the beginning.

### How long does Strix wait for the scan thread to finish before forcing exit?

The `action_custom_quit` method calls `self._scan_thread.join(timeout=1.0)`, giving the thread a maximum of **1.0 seconds** to observe the stop event and shut down. If the thread exceeds this timeout, the TUI proceeds to `self.exit()`, though the thread will continue until Python process termination.

### Is telemetry data saved when I interrupt a scan?

Yes. Both the keyboard quit path and the signal handler path invoke `self.tracer.cleanup()`, which persists any buffered telemetry data before the process exits. This ensures partial scan results are recorded even during an interruption.