Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/onenot8/issueLoop/llms.txt

Use this file to discover all available pages before exploring further.

Live monitoring catches bugs that only appear at runtime — not during a test run. Instead of scanning a finished test report, IssueLoop watches a process or log file continuously: whenever error-matching lines appear in the output, they are buffered, debounced into a single entry, and written to the same JSONL log that the batch test runner uses. From that point on, issueloop tickets picks them up with no extra steps. Use live monitoring alongside the batch workflow for long-running servers, daemons, or any process whose failures surface in production-style logs rather than a test suite.

Mode A — watch a process

In this mode IssueLoop spawns and owns the process. When the process writes matching lines to its output, the watcher captures them.
import issueloop

handle = issueloop.watch_process(
    "myrepo",
    "python3 main.py",
    cwd="/path/to/repo",
    debounce_seconds=3.0,
)

# ... do other work, or just wait ...

issueloop.stop_watch(handle)
The equivalent CLI command runs the watcher in the foreground and blocks until interrupted:
issueloop watch myrepo --command "python3 main.py" --cwd /path/to/repo --debounce 3.0
repo_name
string
required
The repo identifier — must match a name entry in test_manifest.json so that tickets can be associated with the right project.
command
string
required
The shell command to spawn. Executed with shell=True. Both stdout and stderr are merged into a single stream for pattern matching.
cwd
string
Working directory for the spawned process. Defaults to the current directory.
debounce_seconds
float
How many seconds of quiet output to wait before flushing the buffered error block as a single ticket entry. Default is 3.0.
error_patterns
list
A list of compiled re pattern objects to match against incoming lines. Any line matching at least one pattern starts or extends the error buffer. Defaults to the DEFAULT_ERROR_PATTERNS list — see the Error detection patterns section. Override this to narrow or expand what counts as an error for your process.
on_flush
callable
An optional callback invoked each time the debounce timer fires and a buffered error block is written to disk. The callback receives the flushed entry dict as its only argument. Use this to trigger real-time notifications or forward errors to an external system without polling the JSONL log.

Mode B — tail a log file

In this mode the target process is already running and writing to a file. IssueLoop seeks to the end of the file and reads new lines as they arrive — it never re-reads historical content.
import issueloop

handle = issueloop.watch_log_file(
    "myrepo",
    "/var/log/myapp/app.log",
    debounce_seconds=3.0,
)

# ... do other work ...

issueloop.stop_watch(handle)
The equivalent CLI command:
issueloop watch myrepo --log-file /var/log/myapp/app.log --debounce 3.0
If the log file does not exist yet when the watcher starts, IssueLoop polls every 500 ms until it appears, then begins tailing. This means you can start the watcher before the process creates the file.
repo_name
string
required
The repo identifier — must match a name entry in test_manifest.json.
log_path
string
required
Absolute or relative path to the log file to tail. IssueLoop seeks to the end of the file on open and only reads lines written after the watcher started.
command_label
string
A human-readable label stored on each flushed entry as the command field. Defaults to "tail:<log_path>". Set this to something descriptive if you have multiple watchers on the same repo so you can tell their ticket entries apart.
debounce_seconds
float
How many seconds of quiet output to wait before flushing. Default is 3.0.
error_patterns
list
Override the compiled regex patterns used to detect errors. Defaults to DEFAULT_ERROR_PATTERNS.
on_flush
callable
Optional callback invoked with the flushed entry dict each time the debounce timer fires and a block is written to disk.

Error detection patterns

IssueLoop matches each incoming line against a set of compiled regular expressions. A line that matches any pattern starts (or extends) the current error buffer. The default patterns are:
PatternNotes
Traceback (most recent call last)Python exception header
\w*exception\w* (case-insensitive)Any word containing “exception”
\w*error\w* (case-insensitive)Any word containing “error”
^FAILED\bpytest-style failure line at start of line
\bfatal\b (case-insensitive)Fatal messages
panic:Go-style panics
\bpanicked at\bRust-style panics
All patterns are evaluated with re.search, so they match anywhere in the line, not just at the start (except ^FAILED, which is anchored). Once the first matching line arrives, every subsequent line is added to the buffer regardless of whether it matches — this ensures the full traceback or stack trace is captured, not just the first error line.

Debouncing

Errors rarely arrive as a single line. A Python traceback spans many lines; a Go panic dumps a goroutine stack. Without debouncing, each line would become its own ticket entry, flooding the queue with fragments of the same event. The debounce mechanism works like this:
  1. The first matching line starts the buffer and arms a timer set to debounce_seconds (default 3.0).
  2. Each subsequent line resets the timer.
  3. When the timer fires — meaning no new lines arrived for debounce_seconds — the entire buffer is flushed as a single JSONL entry and the buffer is cleared.
This means a continuous burst of error lines (the typical shape of a traceback) becomes one ticket entry, not dozens. Set debounce_seconds lower for interactive workflows where you want faster feedback, or higher for noisy logs where related errors arrive in slow waves.

Stopping a watcher

stop_watch accepts the integer handle returned by watch_process or watch_log_file. It cancels the debounce timer, flushes any remaining buffered lines immediately, and terminates the spawned process (if running in process-watch mode).
issueloop.stop_watch(handle)
To see all currently active watchers in the current process:
watchers = issueloop.list_active_watchers()
# [{"handle": 12345, "repo": "myrepo", "command": "python3 main.py"}, ...]

for w in watchers:
    print(f"handle={w['handle']}  repo={w['repo']}  command={w['command']}")

Output format

Each flushed error block is written to data/logs/run_<repo>.jsonl as a JSON object with the same shape as a batch test runner result:
{
  "timestamp": "2024-01-15T10:23:45.123456+00:00",
  "repo": "myrepo",
  "command": "python3 main.py",
  "test_id": "live_a3f2c901",
  "blocking": true,
  "exit_code": 1,
  "stdout_tail": "",
  "stderr_tail": "Traceback (most recent call last):\n  File \"main.py\", line 42 ..."
}
Because the shape is identical to batch runner output, issueloop tickets myrepo (or issueloop.create_tickets("myrepo")) reads live monitoring entries and batch test entries from the same file and triages them all in one pass. No extra configuration is required.
Live monitoring watchers are in-process threads. If the Python process that called watch_process or watch_log_file exits — cleanly or not — all active watchers stop immediately. Any lines buffered but not yet flushed at exit time will be lost. For long-running monitoring deployments, keep the process alive with a loop or run issueloop watch as a managed service.

Build docs developers (and LLMs) love