-
Notifications
You must be signed in to change notification settings - Fork 0
How it works
Five stages. Only the fourth touches Docker.
run URL
│
├─ 1 RESOLVER network only, runs nothing
├─ 2 STEP MAPPER API steps → YAML step indices
├─ 3 SYNTHESIZER pure function → a temporary workflow
├─ 4 EXECUTOR act → container → your shell
└─ 5 FIDELITY every way local differs from CI
Reads the run, the job, its steps, the commit, the runner labels and the event payload from
the GitHub API, then fetches the workflow file at the run's commit — not at main, which
may have moved on.
It deliberately executes nothing. That is what makes reenact inspect useful without Docker,
keeps the whole thing testable against a recorded corpus with no network, and confines every
risky operation to stage 4.
If the job calls another workflow (uses:), the called file is fetched too — from the run's own
commit when the call is local (uses: ./…), or from the ref when it is not.
The API's step numbers are not YAML step indices. Measured across 243 real jobs, 92% of them have numbers that skip, plus steps the runner injects that exist in no file:
1 Set up job ← synthetic
2 Run git config …
5 Install Go
7 golangci-lint ← the failure
11 Post golangci-lint ← 8,9,10 missing, and synthetic
So synthetic steps are dropped and the rest are matched by name. Where a name contains an expression, the matrix leg is substituted first and the rest is matched as a pattern. Where a name resolves to nothing but an expression — a pattern that would match anything — the mapping is refused rather than guessed.
Produces a temporary workflow containing only the failing job, with:
- the matrix pinned to the failing leg (not deleted — deleting it would make every
${{ matrix.* }}resolve empty) - steps truncated at the breakpoint, plus a final step that holds the container open
- steps that were
skippedin the real run left out entirely, and theif:of steps that did run removed — the run already told us the answer, and re-evaluating the expression locally can produce a different one -
needsand job-levelifdropped, each reported in the fidelity report
The YAML is edited surgically rather than rewritten, so keys reenact does not model — services,
defaults, container, env — survive untouched.
Clones the commit into a temporary directory (never your working tree), runs act with
--bind so .git actually reaches the container, waits for the hold step's token file, then
docker execs you into it. On exit everything is removed, including files the container wrote
as root.
See The fidelity report.