Replico
CI failures, reproduced locally.
GitHub Actions fails → replico <run-url> → the failing command runs on your
machine → you get a reproducible failure with evidence.
Replico is a local-first developer tool. It reads a failed GitHub Actions run, figures out which job failed, which step failed and why, reconstructs the relevant environment (Python version, dependencies, environment variables it can), and replays the failing step on your machine. It then tells you — with honest confidence levels — whether the CI failure was reproduced.
The core promise: turn CI failures into locally reproducible failures whenever Replico can reconstruct the relevant conditions. Replico never claims to be a perfect clone of a GitHub-hosted runner, and never claims a reproduction it cannot back with evidence.
The problem
Developer pushes code → CI fails → Developer reads logs
→ Guesses what went wrong → Changes code → Pushes again → CI fails again
How it works
GitHub Actions fails
↓
replico <run-url> # or: replico run <run-id>
↓
Analyze the failed workflow
↓
Reconstruct the relevant environment
↓
Run the failing command locally
↓
REPRODUCED ✓ / NOT REPRODUCED ✗ / PARTIAL ⚠ (with evidence and parity)
↓
.replico/ saved — iterate: fix code → replico rerun
Quick start
pip install replico
# from the checkout of the repository whose CI failed:
replico https://github.com/example/project/actions/runs/123456789
That's it. No account, no API key, no cloud service. Public repositories work
without any token. Private repositories need either GITHUB_TOKEN in your
environment or the GitHub CLI (gh auth login).
GitHub's API only serves job log downloads to authenticated requests, even for public repositories. Without a token Replico still finds the failed run, job and step, fetches the workflow YAML at the exact commit, and reproduces from the workflow — but the log-level failure analysis needs a token:
export GITHUB_TOKEN=ghp_... # or: gh auth login
Example output (abridged):
─────────────────────────────────────────────────────────────
✓ failed job: Python 3.13
✓ failed step: Run tests
REPLICO REPRODUCTION PLAN
Repository: example/project
Commit: a82f91c
Workflow: Tests
Job: Python 3.13
Failed step: Run tests
Runner: ubuntu-24.04
Ecosystem: python
Detected setup:
✓ checkout
✓ Python 3.13
• pip install -r requirements.txt
Reproducing...
✓ environment ready
✓ dependencies installed
✗ the failing command FAILED locally (exit 1, 2.4 s)
REPLICO RESULT — CI FAILURE REPRODUCED
• the same failing test(s) reproduced locally: tests/test_auth.py::test_login
After a code change:
replico rerun
REPLICO RERUN
✓ environment ready
✓ running the previously failing step…
✓ the failing command PASSED locally
REPLICO RESULT — CI FAILURE NOT REPRODUCED
• the previously reproduced failure no longer occurs locally.
(Wording is careful on purpose: a local pass does not prove CI will pass.)
Installation
Requirements: Python 3.11+, git. Docker is optional but recommended
when the CI runner OS differs from your machine.
pip install replico # installs the `replico` command
# or from source:
pip install -e ".[dev]" # development install
SecretShield (secretshield>=0.4.2) is a real dependency: every log line,
environment value, command output and artifact that Replico displays or saves
passes through SecretShield's detection/redaction (see Security).
Usage
# Reproduce a failed run (the flagship command — same as `replico reproduce …`)
replico https://github.com/owner/repo/actions/runs/123456789
replico reproduce https://github.com/owner/repo/actions/runs/123456789
replico run 123456789 # run id; repository read from git origin
# Iterate after code changes
replico rerun # re-run the saved reproduction
replico status # saved state vs current checkout
replico diff # what changed since the CI failure
# Inspect
replico env # sanitized local environment fingerprint
replico config # effective configuration (no secrets)
replico version
# Hygiene
replico clean # remove .replico/ (confirmed)
# Inside CI (e.g. under `if: failure()`), capture context for later:
replico capture
Common flags:
| Flag | Meaning |
|---|---|
--job <job> |
which failed job to reproduce (multi-job runs) |
--step <name> |
which step to reproduce (default: the failing step) |
--docker / --no-docker |
force / forbid Docker isolation |
--offline |
use only locally saved data — no GitHub requests |
--json |
machine-readable JSON on stdout (all prose goes to stderr) |
--plain |
no colors, no decorations (CI/log capture) |
--yes |
accept confirmations non-interactively |
--verbose / --debug |
more detail (still redacted) |
--clean |
remove an existing .replico/ before reproducing |
Multiple failed jobs
2 failed jobs found.
1. test-python
2. integration-linux
Use --job to select one:
replico <run-url> --job test-python
When exactly one job failed it is selected automatically. The same logic applies inside the job: the first failed step is chosen.
Matrix workflows
Matrix combinations are matched back to the workflow YAML (best effort:
job id, explicit name:, and matrix-expanded display names). The matching
combination's variables are used when rendering the steps.
Supported workflows (v0.1)
| Ecosystem | Status |
|---|---|
Python (actions/setup-python, pip, pytest, python -m unittest, …) |
supported |
Plain shell jobs (run: only, no package managers) |
supported (generic) |
| Node.js / Go / Rust / Java / .NET | detected, reported as unsupported (roadmap v0.3) |
Failing step is a third-party uses: action |
reported as unsupported |
Replico understands checkout/setup actions, dependency install commands
(pip install, -r requirements, pip install -e .), python -m pytest,
environment blocks, working-directory, defaults.run.shell, strategy
matrices, and ${{ matrix.* }} / basic ${{ github.* }} expressions.
Secrets referenced as ${{ secrets.X }} are never fetched; steps that
genuinely require them will fail locally in a deterministic way and Replico
will say so.
Result states
Replico distinguishes — and never conflates:
| State | Meaning |
|---|---|
reproduced |
the failing command failed locally and the failure signature (failing test id / error category) matches CI |
partially_reproduced |
a failure occurred locally but its identity could not be confirmed against CI, or the local run passed under materially different conditions |
not_reproduced |
the failing command passed locally under adequate environment parity |
unsupported |
Replico does not yet know how to reproduce this workflow |
Verbal results are matched by stable exit codes:
| Code | Meaning |
|---|---|
0 |
reproduction succeeded — nothing is failing locally (verdict not_reproduced; a rerun that now passes) |
1 |
reproduced failure still exists (verdict reproduced; a rerun that still fails) |
2 |
could not reproduce (blocked, or partially_reproduced without a local failure) |
3 |
invalid input (bad URL, unknown --job, missing args) |
4 |
authentication problem (private repo without a usable token) |
5 |
unsupported workflow |
6 |
environment/setup problem (missing tool, venv/Docker failure) |
70 |
internal error |
Environment parity
Replico fingerprints your machine (replico env) and compares it with the CI
job:
ENVIRONMENT DIFFERENCES
✓ OS Windows 10/11: CI ubuntu-24.04 ← mismatch would be ✗ / a Docker hint
✗ Python 3.13: using 3.12.4
✓ git
✓ dependencies
✓ environment variables
Environment parity: 72% (estimate — not a guarantee)
Parity is a transparent, weighted heuristic (OS, Python version, isolation, git, dependencies, env vars) — not a claim of byte-for-byte parity with GitHub's runner images. When parity is low and the local run passed, Replico will not let you claim the failure is gone.
Isolation
- Local (default, Python jobs): dependencies are installed into a virtual
environment under
.replico/venv, never into your global environment. --docker: the repository is mounted into a matching image (python:3.13-slim,ubuntu:24.04, …) — the closest match for Linux runners and the recommended mode when CI ran on a different OS.- Automatic mode picks Docker when the CI runner OS (or requested Python version) is not available locally and Docker is running.
Replico never runs sudo, administrator commands or destructive filesystem
operations without explicit confirmation. Commands extracted from workflow
files are audited first (replico/security/guard.py); risky ones require
--yes or an interactive confirm, and elevation is never performed for you.
Security
Replico is local-first and privacy-conscious:
- Network access is limited to the GitHub API (plus dependency downloads the workflow itself requests). No source code, logs, environment values or artifacts are uploaded anywhere. There is no telemetry.
- Tokens come from
GITHUB_TOKEN/GH_TOKEN/ the GitHub CLI and travel only in theAuthorizationheader of API requests. They are never logged, displayed or saved. - SecretShield (
secretshield>=0.4.2) is used wherever sensitive content could appear:- CI logs and command output are scanned/redacted before display or
persistence (
redact/detect), secretshield.enable()protectsstdout/stderrand the logging module as a last line of defense,replico/security/redaction.pyis the single adapter between Replico and SecretShield; Replico adds literal known-secret redaction (values from your environment) on top, because SecretShield is pattern/entropy based and cannot know that a low-entropy string is your password.
- CI logs and command output are scanned/redacted before display or
persistence (
- Secret-like environment variables are shown as
NAME = present— never their values — inreplico env, fingerprints, JSON output,--debug, and everything saved under.replico/. - Environment values are kept out of child-process environments unless they
are workflow literals that CI itself would set;
${{ secrets.* }}is never resolved or injected. - Malicious inputs are handled defensively: YAML is parsed with a
budgeted/memoized engine (alias-expansion bombs are neutralized),
repository/job names are validated before touching paths or URLs, command
lines are audited, subprocesses are spawned without
shell=Truefor Replico's own commands, and env var names are validated. - Workflow YAML is stored redacted under
.replico/; seereplico/security/and the tests intests/for the details.
Privacy
Your repository stays on your machine. Replico makes no network calls beyond GitHub API requests that are required to read the run, its logs and its workflow file, plus whatever the workflow itself runs (dependency installs). There is no Replico server, no account, and telemetry is not collected — if telemetry is ever introduced it will be opt-in only.
Limitations (honest)
- Replico does not clone GitHub's runner images. Tools preinstalled on GitHub-hosted runners (compilers, system libraries, caches) are generally absent locally; parity numbers reflect that.
- Only
run:steps are replayed. Third-party actions cannot be executed locally without their container/runtime. - v0.1 covers Python workflows well and plain shell jobs; Node/Go/Rust are detected and reported as unsupported rather than half-executed.
- Log analysis is heuristic. When Replico cannot extract a confident failure signature it says so instead of guessing.
- Multi-line steps are replayed as one script (matching GitHub's behavior)
with the shell GitHub would use (
bash -eo pipefail, pwsh on Windows).
Architecture
replico/
├── cli.py argparse entry point, exit-code mapping
├── flows.py reproduce / rerun orchestration
├── pipeline.py run → plan → execute → verdict engine helpers
├── cmds.py status / diff / env / clean / config / capture
├── config.py .replico.toml (optional) + defaults
├── ui.py safe console output (rich, sanitized, JSON mode)
├── errors.py exceptions bound to stable exit codes
├── github/ URL parsing, REST client (token-safe), job/step models
├── workflow/ bomb-safe YAML parser, workflow model, job matcher,
│ environment/dependency detection
├── environments/ ecosystem adapters (base, python), fingerprinting
├── execution/ shell runner, Docker isolation
├── analysis/ log analysis (500 lines → 12 relevant), classifier
├── storage/ .replico/ store (redacted artifacts)
└── security/ SecretShield adapter, sanitizer, command/path guards
Ecosystems plug in behind EcosystemAdapter:
class EcosystemAdapter(ABC):
def detect(self, analysis: JobAnalysis) -> EcosystemDetection: ...
# see environments/base.py — Node (planned v0.3) already registers
Development
pip install -e ".[dev]"
pytest # offline test suite (mocked GitHub)
ruff check . && ruff format --check .
mypy src/replico
python -m build # package validation
Replico's own CI
.github/workflows/ci.yml tests Replico itself on Windows/Ubuntu/macOS and
Python 3.11–3.14, running tests, lint, type checks, build and package
validation. Dogfooding goal: Replico should eventually reproduce its own CI
failures (replico capture in a if: failure() step is the first step).
Roadmap
- v0.1 (this release) — GitHub Actions (public repos), failed job/step
detection, Python reproduction, honest verdicts,
.replico/, Windows / Linux / macOS, offline test suite, SecretShield integration. - v0.2 — private repos everywhere (already works with a token), richer
log analysis,
rerun/status/JSON polish, better environment comparison, Docker auto-detection improvements. - v0.3 — Node.js, Go, Rust adapters, matrix/multi-job refinements, deeper failure classification.
- v0.4+ — GitHub Action, PR comments, reproduction artifacts, local failure history, IDE integrations.
Contributing
Issues and pull requests welcome. Before contributing, read the security
model (replico/security/) — secret safety is non-negotiable. All tests must
run offline; GitHub interactions are mocked.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file replico-0.1.0.tar.gz.
File metadata
- Download URL: replico-0.1.0.tar.gz
- Upload date:
- Size: 90.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35555180c23e894277eec69973dfb94d306d43336dac4db89d6cec6e0247d4df
|
|
| MD5 |
1a5edafd39f586142854c50c99a2ad75
|
|
| BLAKE2b-256 |
ffd2fdbbdb22997a23c069c22c9274a590d1a5c4d9eda5690fdfbed383848944
|
File details
Details for the file replico-0.1.0-py3-none-any.whl.
File metadata
- Download URL: replico-0.1.0-py3-none-any.whl
- Upload date:
- Size: 85.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ee20504f3418de642ac890aaa4f8f09085ce499594448f43c73fed18d4bceb2
|
|
| MD5 |
d3e5f322c5fdd9e0cea242f83b2f1c50
|
|
| BLAKE2b-256 |
58ed1e5b00f93faf54ad4ba9cb56520a8f41ce558c594b1d6f46eb1473395773
|