RepoTrace
RepoTrace turns a failing command into something you can keep: a structured bug
report carrying the command, its output, and the environment it ran in — which
you can then re-run to check whether the failure still reproduces, render as a
Markdown document, or convert into a committed pytest regression test.
The point is reproducibility. A bug report that only says "it failed" is hard to act on; one that stores the exact argv, exit code, stdout, stderr, working directory, and interpreter version can be replayed at any later date and will tell you honestly whether the failure is still there.
Installation
RepoTrace requires Python 3.12 or newer.
# From a checkout, editable install (recommended for development)
uv sync
# Or with pip
pip install -e .
This provides the repotrace console script.
Quick start
Capture a failing command. Note that the command is passed through verbatim, so
its own flags (-c, --flag, …) are not interpreted by RepoTrace:
repotrace capture python -m pytest tests/test_thing.py -q
This prints the captured report id and exit code, and writes a JSON report to
.repotrace/reports/<id>.json. capture exits with the command's own exit
code, so it can sit directly in a shell pipeline.
repotrace list
repotrace show <id>
# Does it still fail the same way?
repotrace replay <id>
# Or does the whole corpus still reproduce?
repotrace replay-all
The workflow
capture ──► report store ──► replay ──► report / generate
│ │
└──────────── list / show ◄────┘
- Capture a failing command. Its argv, output, exit code, working
directory, and host metadata are recorded as a
BugReportand persisted as one JSON file under the store. - List and inspect what you have stored.
- Replay a report to re-run the recorded command and compare the result against the record.
- Report a report as Markdown, optionally with its replay verdict baked in.
- Generate a standalone regression test that re-runs the recorded command and asserts nothing changed.
Command reference
capture
Run a command, record its trace, and persist a bug report.
repotrace capture [OPTIONS] COMMAND...
| Option | Description |
|---|---|
--store, -s PATH |
Report store directory. Default .repotrace/reports. |
--cwd PATH |
Working directory for the run. |
--timeout SECONDS |
Seconds to wait before the command is killed. |
--title TEXT |
Title for the report (default: a summary of the command). |
--id TEXT |
Report id (default: a 12-char hash of the argv). |
--severity TEXT |
Severity, e.g. high. |
--tags TEXT |
Comma-separated tags, e.g. flaky,ci. |
--keep-going |
Exit 0 even when the captured command failed. |
repotrace capture --severity high --tags flaky,ci \
python -c "import sys; print('boom'); sys.exit(7)"
Captured stdout and stderr are echoed to the console as well as stored.
list
Print the id of every stored report, one per line, followed by a count. Prints
no reports stored for an empty store.
repotrace list [--store PATH]
show
Print one report's fields and metadata as a table.
repotrace show [--store PATH] REPORT_ID [--markdown]
--markdown renders the full Markdown document instead of the summary table.
replay
Re-run a stored command and compare it with the recorded failure.
repotrace replay [OPTIONS] REPORT_ID
| Option | Description |
|---|---|
--store, -s PATH |
Report store directory. |
--cwd PATH |
Working directory (default: the recorded one). |
--timeout SECONDS |
Seconds to wait before the command is killed. |
--exit-code-only |
Compare exit codes only, ignoring captured output. |
--keep-going |
Exit 0 even when the replay no longer reproduces. |
Use --exit-code-only when the captured output contains machine-specific paths
that will legitimately differ between machines.
replay-all
Replay every stored report and summarise the outcomes.
repotrace replay-all [OPTIONS]
| Option | Description |
|---|---|
--store, -s PATH |
Report store directory. |
--cwd PATH |
Working directory (default: each recorded one). |
--timeout SECONDS |
Seconds to wait before each command is killed. |
--exit-code-only |
Compare exit codes only, ignoring captured output. |
--output, -o PATH |
Write the sweep as Markdown to this file. |
--overwrite |
Replace the output file if it already exists. |
--keep-going |
Exit 0 even when some replays no longer reproduce. |
Reports that raise during replay — for example a report with no usable command — are counted as failures rather than aborting the sweep.
report
Render a stored report as Markdown.
repotrace report [OPTIONS] REPORT_ID
| Option | Description |
|---|---|
--store, -s PATH |
Report store directory. |
--output, -o PATH |
Write the Markdown report to this file. |
--overwrite |
Replace the output file if it already exists. |
--replay |
Replay the report first and include the verdict. |
--no-metadata |
Omit the metadata section. |
--no-events |
Omit the event listing. |
generate
Emit a standalone pytest regression test from a stored report.
repotrace generate [OPTIONS] REPORT_ID
| Option | Description |
|---|---|
--store, -s PATH |
Report store directory. |
--output, -o PATH |
Destination test file (default: stdout). |
--name TEXT |
Test function name (default: derived from the report id). |
--exit-code-only |
Assert the exit code only, not captured output. |
--overwrite |
Replace the output file if it already exists. |
The generated module embeds the command and expected output as literals, so it runs with no RepoTrace installation and can be committed next to the project.
version
Print the installed RepoTrace version.
Exit codes
RepoTrace is designed to be usable from scripts and CI, so its exit codes are part of the interface:
| Code | Meaning |
|---|---|
0 |
Success. |
1 |
A captured command failed, or a replay no longer reproduces. |
2 |
Usage or lookup error: a missing report, a missing binary, an unwritable output path. |
capture propagates the captured command's own exit code. Use --keep-going on
capture, replay, or replay-all to force 0 regardless.
Examples
Capture and inspect
$ repotrace capture python -c "import sys; print('boom'); sys.exit(7)"
captured 5303b4f97c12
exit code 7
boom
$ repotrace list
5303b4f97c12
1 report(s)
$ repotrace show 5303b4f97c12
Report 5303b4f97c12
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Field ┃ Value ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ id │ 5303b4f97c12 │
│ title │ Command failed: python -c import sys; print('boom'); sys.exit(7) │
│ status │ open │
│ severity │ — │
│ tags │ capture │
│ created_at │ 2026-10-18 04:05:48.298051+00:00 │
└─────────────┴────────────────────────────────────────────────────────────────┘
metadata
cwd: '/tmp/demo'
exit_code: 7
events: [...]
Replay a single report
$ repotrace replay 5303b4f97c12
5303b4f97c12 `matched` — no differences
$ echo $?
0
Sweep the whole store
$ repotrace replay-all
Replay summary
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Report ┃ Outcome ┃ Exit code ┃ Differences ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 5303b4f97c12 │ reproduced │ 7 │ none │
└──────────────┴────────────┴───────────┴─────────────┘
replayed 1 · reproduced 1 · differs 0 · failed 0 · reproducibility 100%
Render a Markdown report with its verdict
$ repotrace report 5303b4f97c12 --replay
5303b4f97c12 `matched` — no differences
# Replay report — reproduced
## Summary
**ID:** `5303b4f97c12`
**Status:** open
**Severity:** —
**Tags:** capture
**Created:** 2026-10-18 04:05:48 UTC
## Replay outcome
**Outcome:** `matched` (reproduced)
**Exit code:** 7 (recorded 7)
**Working directory:** `/tmp/demo`
**Command:** `python -c import sys; print('boom'); sys.exit(7)`
Differences: none
## Metadata
- **cwd:** /tmp/demo
- **exit_code:** `int`
- **events:** `list`
- **environment:** `dict`
## Events
- 0. `command`: python -c import sys; print('boom'); sys.exit(7)
- 1. `stdout`: boom
- 2. `exit`: exit code 7
Write a Markdown sweep to a file
$ repotrace replay-all -o sweep.md
wrote sweep.md
$ cat sweep.md
# Replay summary
**Reports replayed:** 1
**Reproduced:** 1
**No longer reproduce:** 0
**Failed to run:** 0
**Reproducibility:** 100%
| Report | Outcome | Exit code | Differences |
| --- | --- | --- | --- |
| `5303b4f97c12` | `matched` | 7 | none |
Generate a regression test
$ repotrace generate 5303b4f97c12 -o tests/test_regression_5303.py
wrote tests/test_regression_5303.py
$ pytest tests/test_regression_5303.py
. [100%]
1 passed in 0.01s
The generated test looks like this:
"""Regression test generated by RepoTrace.
Report: 5303b4f97c12
Title: Command failed: python -c import sys; print('boom'); sys.exit(7)
Recorded command: python -c import sys; print('boom'); sys.exit(7)
Recorded exit code: 7
"""
import subprocess
COMMAND = ['python', '-c', "import sys; print('boom'); sys.exit(7)"]
EXPECTED_EXIT_CODE = 7
EXPECTED_STDOUT = 'boom\n'
EXPECTED_STDERR = ''
def test_regression_5303b4f97c12() -> None:
result = subprocess.run(COMMAND, capture_output=True, text=True)
assert result.returncode == EXPECTED_EXIT_CODE
assert result.stdout == EXPECTED_STDOUT
assert result.stderr == EXPECTED_STDERR
Library usage
Every command has a programmatic equivalent, so RepoTrace can be embedded in tooling rather than driven through the shell.
from repotrace.capture import capture_command
from repotrace.replay import replay_from_store
from repotrace.report import format_report
from repotrace.storage import ReportStore
store = ReportStore(".repotrace/reports")
# Capture and persist.
trace, report = capture_command(
["python", "-m", "pytest", "tests/"],
store=store,
severity="high",
)
# Replay it.
result = replay_from_store(report.id, store)
print(result.outcome, result.differences) # Outcome.MATCHED []
# Render a Markdown report with the verdict.
print(format_report(report, result=result))
Module map
| Module | Responsibility |
|---|---|
repotrace.storage |
The BugReport schema and the JSON-file ReportStore. |
repotrace.capture |
Trace, TraceEvent, run_capture, build_report, capture_command. |
repotrace.generator |
Reproduction plus generate_test / write_test. |
repotrace.replay |
Outcome, ReplayResult, ReplaySummary, and the replay* functions. |
repotrace.report |
format_report, format_replays, and their write counterparts. |
repotrace.cli |
The repotrace Typer application. |
Key API
from repotrace.capture import Trace, run_capture, build_report, capture_command
from repotrace.storage import BugReport, ReportStore
from repotrace.generator import Reproduction, generate_test, write_test
from repotrace.replay import (
Outcome, ReplayResult, ReplaySummary,
replay, replay_report, replay_from_store, replay_all, diff,
)
from repotrace.report import (
format_report, write_report, format_replays, write_replays,
render_result_line, build_summary, outcome_label,
)
Notable signatures:
replay(source, *, cwd=None, timeout=None, match_output=True) -> ReplayResult
replay_from_store(report_id, store, *, cwd=None, timeout=None,
match_output=True) -> ReplayResult
replay_all(store, *, cwd=None, timeout=None, match_output=True) -> ReplaySummary
format_report(report, *, result=None, include_metadata=True,
include_events=True) -> str
format_replays(summary, *, max_rows=None) -> str
generate_test(source, *, function_name=None, match_output=True) -> str
replay accepts a Reproduction, a Trace, or a BugReport, so the same
call works on a fresh capture or a persisted report. diff(result) returns a
JSON-serialisable mapping of expected-versus-observed values.
Report store
Reports live as one JSON file per report under the store root, which defaults to
.repotrace/reports and is created on demand:
.repotrace/
└── reports/
├── 5303b4f97c12.json
└── a1b2c3d4e5f6.json
Because the store is plain JSON, reports can be inspected, diffed, and committed
alongside a project. Point the CLI at a different location with --store, or
construct ReportStore(path) directly.
Development
# Install the package and dev dependencies
uv sync
# Run the test suite
uv run pytest
# Lint and format
uv run ruff check src tests
uv run ruff format src tests
The test suite covers each module in isolation (tests/test_storage.py,
test_capture.py, test_generator.py, test_replay.py, test_report.py) plus
the CLI surface (tests/test_cli.py).
Tech stack
- Python 3.12+
- Typer — CLI
- Pydantic v2 — data validation
- Rich — console output
- pytest — testing
Contributing
See CONTRIBUTING.md.
License
Metadata
Release files for repotrace 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| repotrace-0.1.0.tar.gz | 22.5 kB | With provenance | Details for repotrace-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| repotrace-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for repotrace-0.1.0-py3-none-any.whl |
Total release size: 49.5 kB
Release files / repotrace-0.1.0.tar.gz
| Download URL | repotrace-0.1.0.tar.gz |
|---|---|
| Size | 22.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
46079cd9b6de7b3456cbb6ad03809b5eb1a3f3767bfbcd7bdc51243c6fda01be
|
|
BLAKE2b-256 checksum How to use checksums |
7770095ad55189b4ad916054b6865ff9d0f534803934e880efd977e3db212161
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / repotrace-0.1.0-py3-none-any.whl
| Download URL | repotrace-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c8ea1a7df3da1ead39eabe35a346a9ee0e124411edbe6119b48cf51513d7d4e8
|
|
BLAKE2b-256 checksum How to use checksums |
4e82c3dd554e92a7eb0b277d0515dabe2dec95b61ab61eef79acb8cd805bf186
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logSecurity for repotrace 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for repotrace-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.