Skip to main content

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 the Authorization header 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() protects stdout/stderr and the logging module as a last line of defense,
    • replico/security/redaction.py is 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.
  • Secret-like environment variables are shown as NAME = present — never their values — in replico 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=True for Replico's own commands, and env var names are validated.
  • Workflow YAML is stored redacted under .replico/; see replico/security/ and the tests in tests/ 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

replico-0.1.0.tar.gz (90.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

replico-0.1.0-py3-none-any.whl (85.6 kB view details)

Uploaded Python 3

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

Hashes for replico-0.1.0.tar.gz
Algorithm Hash digest
SHA256 35555180c23e894277eec69973dfb94d306d43336dac4db89d6cec6e0247d4df
MD5 1a5edafd39f586142854c50c99a2ad75
BLAKE2b-256 ffd2fdbbdb22997a23c069c22c9274a590d1a5c4d9eda5690fdfbed383848944

See more details on using hashes here.

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

Hashes for replico-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6ee20504f3418de642ac890aaa4f8f09085ce499594448f43c73fed18d4bceb2
MD5 d3e5f322c5fdd9e0cea242f83b2f1c50
BLAKE2b-256 58ed1e5b00f93faf54ad4ba9cb56520a8f41ce558c594b1d6f46eb1473395773

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.3

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page