Inspect any GitHub Actions job from your terminal.
Turn a job URL into the facts a maintainer needs before attempting a fix.
Quick start · What you get · Compare · JSON output · First contributions · Impact · Roadmap · Contributing
npx runreplay inspect https://github.com/OWNER/REPO/actions/runs/RUN_ID/job/JOB_ID
# Resolve the workflow source and Action revisions for the same historical job
npx runreplay resolve https://github.com/OWNER/REPO/actions/runs/RUN_ID/job/JOB_ID
# Compare a failed job with the last strictly comparable successful job
npx runreplay compare https://github.com/OWNER/REPO/actions/runs/RUN_ID/job/JOB_ID --baseline last-successfulnpx downloads the public CLI when needed; no clone or global install is required. RunReplay works without a token for public repositories, subject to GitHub's anonymous API limits. For private repositories, set a fine-grained GITHUB_TOKEN with read access to Actions and Contents:
GITHUB_TOKEN=github_pat_... npx runreplay inspect <job-url>Do not paste tokens into an issue, shared shell history, or CI log.
For GitHub Enterprise Server, pass the browser job URL and explicitly select the REST API base. RunReplay does not infer API hosts from job URLs:
GITHUB_TOKEN=github_pat_... npx runreplay inspect \
https://ghe.example.com/OWNER/REPO/actions/runs/RUN_ID/job/JOB_ID \
--api-base https://ghe.example.com/api/v3Use an Enterprise token with read access to Actions and Contents for the target repository. --api-base must be an HTTPS API base URL and must not include credentials, query strings, or fragments.
When GitHub rejects a request, RunReplay keeps the original status code and safe API message, then adds a short next step for common cases: invalid tokens, missing Actions or Contents read access, rate limits, missing jobs, and expired logs or artifacts.
To install it once instead:
npm install --global runreplay
runreplay inspect <job-url>This is a real inspection of a public failed GitHub Actions job. RunReplay identifies the commit, hosted runner, failed step, and attached artifacts before anyone proposes a patch.
| Evidence | Why it matters |
|---|---|
| Commit SHA and branch | Anchor the investigation to the code that actually ran. |
| Workflow event and job conclusion | Separate a failing job from a broader workflow summary. |
| Runner labels and timing | Show the available execution context GitHub exposes. |
| Per-step results | Point directly to the failed command stage. |
| Logs API URL and artifacts | Preserve the available trail for deeper investigation. |
Use --json when the inspection feeds Claude, jq, a CI bot, or another tool:
node dist/cli.js inspect <job-url> --json > inspection.jsonThe public schema is versioned from day one:
{
"schemaVersion": "1.0",
"repository": "actions/checkout",
"runId": 123,
"jobId": 456,
"commitSha": "…",
"event": "push",
"runner": { "labels": ["ubuntu-latest"] },
"steps": [],
"artifacts": [
{
"id": 7,
"name": "test-results",
"sizeInBytes": 42,
"expired": false,
"availability": "available",
"createdAt": "2026-07-26T10:02:00Z",
"updatedAt": "2026-07-26T10:03:00Z",
"expiresAt": "2026-08-25T10:02:00Z",
"digest": "sha256:…",
"workflowRun": {
"id": 123,
"repositoryId": 99,
"headRepositoryId": 100,
"headBranch": "main",
"headSha": "…"
},
"apiUrl": "https://api.github.com/repos/actions/checkout/actions/artifacts/7",
"archiveDownloadUrl": "https://api.github.com/artifacts/7/zip"
}
],
"redactions": []
}Artifact metadata is additive within schema 1.0: older consumers can ignore
the extra fields, and missing GitHub fields are emitted as null. availability
is derived from GitHub's expired flag so human and JSON output use the same
state.
inspect tells you what GitHub still exposes about a job. resolve adds the workflow source from the commit that ran and identifies the revision behind each supported uses: declaration:
npx runreplay resolve <job-url> --json > manifest.jsonRunReplay never resolves a mutable tag today and calls it historical truth. Every Action record declares its evidence level:
| Evidence | Meaning |
|---|---|
runtime-log |
GitHub Runner recorded the exact SHA downloaded by this job. This is the strongest historical evidence. |
declared-full-sha |
The workflow declared an immutable 40-character SHA. |
github-api-current-ref |
GitHub resolved a mutable branch or tag now. It is useful context, not proof of the old run. |
unresolved |
RunReplay lacks trustworthy evidence and says why instead of guessing. |
Version 0.2 supports repository Actions, including actions declared from repository subdirectories. It explicitly reports local Actions, Docker Actions, dynamic expressions, and reusable workflows as unresolved where their execution cannot yet be proven.
compare turns two historical jobs into a CI diff. It reports changes in workflow source, declared and historically observed Action revisions, runner labels, steps, artifacts, timing, commits, and changed files.
# Explicit baseline: first URL is the failed/target job, second is its baseline
npx runreplay compare <failed-job-url> <baseline-job-url>
# Automatic baseline: only an exact earlier successful match is accepted
npx runreplay compare <failed-job-url> --baseline last-successful
# Stable machine-readable report
npx runreplay compare <failed-job-url> --baseline last-successful --json > comparison.jsonFor last-successful, RunReplay requires the same repository workflow, job name, event, branch, and runner labels. If no such completed successful job exists, it returns:
{
"schemaVersion": "1.0",
"baseline": null,
"reason": "no-comparable-successful-job"
}RunReplay reads successful workflow runs page by page, up to 1,000 runs. If that limit is reached before an exact match is found, it reports baseline-search-limit-reached and searchedRuns: 1000 rather than claiming that no baseline exists.
changedInputs is deliberately descriptive, not an AI diagnosis: a changed Action SHA or runner image is an investigation lead, not proof of the failure's cause.
RunReplay is used on public CI failures before a fix is proposed. The first documented investigation is actalog case 001: a timezone-dependent Vue test, diagnosed from a failed-vs-successful CI comparison and fixed in an external PR.
The current evidence counters live in IMPACT.md. They deliberately count only public, verifiable outcomes.
RunReplay is an inspector, not a VM time machine. It does not claim to restore a completed runner's filesystem, caches, secrets, service-container state, or other data GitHub did not retain.
The resolve manifest finds the workflow source at the inspected commit and identifies supported Action revisions with explicit evidence. Matrix expansion, reusable workflow traversal, nested/composite Actions, and a best-effort local replay path remain future work. Every replay claim must be backed by explicit evidence and verification. See the roadmap.
job URL
│
├── URL parser ── validates owner, repository, run, and job IDs
├── GitHub API ── retrieves job, workflow run, and artifact metadata
├── inspection ── formats evidence for humans or the versioned JSON schema
└── resolve manifest ── historical workflow source + Action SHA evidence
└── compare ── strict baseline matching + factual CI diff
These small, unclaimed issues are a good way to get started. See CONTRIBUTING.md for the full contribution workflow; maintainers review focused pull requests promptly.
| Issue | Typical size |
|---|---|
| #12: malformed GitHub Actions job URL tests | One test file |
| #13: artifact formatting edge-case tests | Two test files |
| #14: expired-log (410) fixture scenario | Fixture plus integration test |
| #15: rate-limit fixture scenarios | Fixture plus integration test |
| #19: first-time contributor walkthrough | One short documentation section |
Before opening a pull request, run:
npm ci
npm test
npm run check
git diff --checkThe first public contribution paths are intentionally small and useful:
- recorded GitHub API fixtures;
- redacted inspection-manifest export (design in docs/inspection-manifest.md).
Read CONTRIBUTING.md, run the checks, and keep each pull request focused.
git clone https://github.com/shleder/runreplay.git
cd runreplay
npm install
npm test
npm run check