# Replay an agent skill on every PR with the Reelier GitHub Action

This guide turns a recorded agent skill into a check that replays on every pull request and blocks the merge the moment the tool calls drift — with no model in the loop and 0 tokens per run. It is the plumbing behind "run agent tests in CI": a frozen `.skill.md` in your repo, a workflow that replays it, and a red check when a recorded assertion stops holding.

GitHub Actions is where you RUN a skill, not where Reelier captures one — `reelier scan` has no CI transcript to read. So you record the skill first on the coding agent you work in, commit the file, and wire the `seldonframe/reelier@v1` Action to gate it. Four steps follow: prove the skill locally, add the workflow, open a PR, watch it fail on drift. Every command is real and every output is verbatim.

Markdown twin of https://www.reelier.com/guides/github-action-agent-replay — a step-by-step Reelier how-to. Every command below is a real Reelier command and every output block is verbatim, never a mockup.

## Before you start

- A recorded skill committed to the repo you want to guard — a plain `.skill.md` file. Record it first on your coding agent (freeze a Claude Code session, or wrap an MCP server with `reelier mcp --wrap` on Cursor, Codex or Windsurf); the linked guides cover recording.
- A read-only skill makes the best gate. Reelier replays MCP and tool-call steps plus the `http.get` / `http.post` builtins — not `Bash`, `Read`, `Edit`, `Write` or `Grep` — so pick a run whose value was in the API calls, like a data pull or a status check.
- A GitHub repository with Actions enabled. No account, API key or model is needed at `--max-level 0` — the replay costs 0 tokens, and a `cloud-key` is optional.

## 01 · Prove the skill passes locally first

The Action runs one command for you — `reelier run <skill> --max-level 0` — so run it yourself before wiring CI. It re-executes the recorded tool calls against the live systems and checks each recorded assertion. The repo's bundled `npm-info.skill.md` (a single read-only `http.get` to the npm registry) reproduces the receipt below exactly.

The skill asserts minimally — reelier's minimal-assertion rule: a step asserts a shape, not a brittle value, and only when it recorded a clean result. Here the `http.get` step asserts `status == 200` and nothing else. At `--max-level 0` — the default — no LLM client is ever constructed, so the replay costs 0 tokens and exits `0` when every assertion holds. That exit code is what the check gates on.

```bash
reelier run npm-info.skill.md --max-level 0
```

```
✓ Step 1 — Fetch registry metadata and extract version/license/tarball [passed] 461ms

PASSED: 1/1 steps ok, 0 failed, 461ms total
```

## 02 · Add the seldonframe/reelier@v1 workflow

Create `.github/workflows/reelier.yml`. The Action is a composite step published at `seldonframe/reelier`, tag `@v1`; after an `actions/checkout@v4` it installs `reelier@^0.12`, runs the same `reelier run <skill> --max-level 0` you just ran, writes the receipt as the job summary, and fails the check on the run's own exit code — you never wire the pass/fail yourself.

Trigger it `on: pull_request` so a drifted step blocks the merge, and scope it with `paths: ["my-job.skill.md"]` so it only fires when the skill changes; add a `schedule:` cron for a standing dated proof on a public repo. In the `with:` block, set `skill:` to your committed file and keep `max-level: "0"` — the default that needs no credentials. The full copy-paste YAML, the inputs table, and the `passed` / `run-record-path` outputs live on the platform page; this guide links it rather than restating it.

## 03 · Open a pull request and read the receipt

Push the workflow and the skill on a branch and open a PR. The Action replays the skill and posts a receipt as the run's job summary: the skill name, a PASSED / FAILED verdict, and a per-step table of `passed` / `unchecked` / `skipped` / `failed` with the duration and token spend (0 at level 0).

Read the `unchecked` column honestly — a step that ran without error but asserted nothing is reported as `unchecked`, never folded into `passed`. A green check means every recorded assertion actually held, not merely that the CLI ran. The exact markdown the Action writes is the closing receipt below.

## 04 · Watch it turn red when the tool calls drift

The gate earns its keep on the day the recorded run stops matching. If an upstream contract changes — the endpoint returns a `500`, a field moves — the recorded assertion (`status == 200`) no longer holds, the step fails, `reelier run` exits non-zero, and the Action's final step re-raises that exit code. The check turns red and the PR cannot merge on a drifted tool call — regression testing for agent tool calls, with no model in the loop.

When the change was intentional, update the baseline as you would a snapshot: re-record and recompile the `.skill.md`, or edit the failing assertion directly — it is markdown in your repo. Replay stays read-only by default, so a PR or scheduled gate never re-fires a recorded write; a write step only re-executes under `--allow-writes` (idempotent writes only — a destructive step still needs `--yes`), which the Action does not pass.

## The receipt

```
## Reelier replay: `npm-info.skill.md`

**Skill:** `npm-info`  
**Result:** ✅ PASSED

| steps | passed | unchecked | skipped | failed | duration |
| --- | --- | --- | --- | --- | --- |
| 1 | 1 | 0 | 0 | 0 | 461ms |

> `unchecked` = ran without error but asserted nothing — the honest-success rule (SPEC.md §4.3) means it is never counted as a pass.

_Ledger: not pushed (no `cloud-key` input set)._
```

The receipt the Action posts as its GitHub job summary — the markdown you see on the PR's checks tab. Produced on 2026-07-22 with `reelier` v0.14.0 by running the Action's own `.github/scripts/gha-summary.mjs` against the `.reelier/runs/npm-info.jsonl` record that `reelier run npm-info.skill.md --max-level 0` wrote (the 461ms matches Step 1 — one record, two views). The collapsible per-step detail block is trimmed here for space; nothing else is edited. 0 tokens; `cloud-key` was unset, so the replay was local-only and still wrote this summary.

## FAQ

**The Action didn't fire on my pull request — why?**

Usually the `paths:` filter scoped it to skill changes, so a PR that doesn't touch the `.skill.md` won't trigger it — drop or widen `paths:` to run on every PR. Also confirm the workflow file exists on the base branch: GitHub only runs the version of a workflow already on the target branch.

**How do I gate on more than one skill?**

The `skill` input takes a single path. For several skills, call the action once per skill — repeat the step (or use a `matrix`) with a different `skill:` each time. Each replay fails the check independently, so one drifted skill is enough to block the merge.

**Do I need a Reelier account or a cloud-key?**

No. `cloud-key` is optional — leave it unset for a local-only replay that still writes the job summary and still gates the check. The pass/fail depends only on `reelier run`'s exit code, never on Cloud. Set `cloud-key` (as a GitHub secret) only to push the receipt to your ledger after a passing run.

## Related

- [Reelier for GitHub Actions — the canonical workflow YAML, inputs and outputs](https://www.reelier.com/for/github-actions)
- [Agent regression testing — the pattern this CI gate enforces, defined](https://www.reelier.com/learn/agent-regression-testing)
- [Guide: record and snapshot-test the skill before you gate it in CI](https://www.reelier.com/guides/snapshot-test-claude-code-workflows)
- [Guide: gate a scheduled replay on drift with reelier diff](https://www.reelier.com/guides/ci-gate-for-agent-drift)
- [Agent drift — the failure mode the red check is there to catch](https://www.reelier.com/learn/tool-call-drift)

## Sources

- [seldonframe/reelier — the open-source (AGPL-3.0) CLI and Action every command here runs](https://github.com/seldonframe/reelier)
- [action.yml — the Action's inputs (skill / max-level / cloud-key) and composite steps](https://github.com/seldonframe/reelier/blob/main/action.yml)
- [reelier-replay.example.yml — a ready-to-copy workflow template in the repo](https://github.com/seldonframe/reelier/blob/main/.github/workflows/reelier-replay.example.yml)
- [GitHub Actions: the pull_request event — how the PR trigger fires](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request)

Other guides: [How to snapshot-test a Claude Code workflow](https://www.reelier.com/guides/snapshot-test-claude-code-workflows) · [How to record MCP tool calls with a wrap proxy](https://www.reelier.com/guides/record-mcp-tool-calls) · [How to replay an agent workflow at 0 LLM tokens](https://www.reelier.com/guides/replay-agent-workflow-zero-tokens) · [How to gate CI on agent drift](https://www.reelier.com/guides/ci-gate-for-agent-drift) · [Test your MCP server for regressions by replaying a golden run](https://www.reelier.com/guides/test-mcp-servers) · [Run a deterministic cron agent by scheduling a recorded replay](https://www.reelier.com/guides/deterministic-cron-agents) · [Build an agent audit trail from Reelier run receipts](https://www.reelier.com/guides/agent-audit-trail-receipts) · [Configure BYOK escalation levels: L0, L1, and L2](https://www.reelier.com/guides/byok-escalation-levels) · [Compile a skill from an existing session with reelier from-session](https://www.reelier.com/guides/from-session-compile) · [Diff two agent runs with reelier diff (exit 1 on drift)](https://www.reelier.com/guides/diff-two-agent-runs) · [Convert an LLM agent skill into a deterministic, assertion-checked skill](https://www.reelier.com/guides/convert-agent-skill-deterministic)
