Skip to content

Cache recorded cassettes in GitHub Actions

Rerunner's pytest plugin replays cassettes by default, so a pull request can run agent tests without API calls or model credentials. Keep cassette paths stable and cache only the recorded .jsonl files that the tests use.

1. Use a stable cassette path

Point the marker at a repository-relative path, for example:

import pytest


@pytest.mark.rerunner("tests/fixtures/agent_run.jsonl")
def test_agent_decision(rerunner_transport): ...

Record or refresh cassettes locally when the interaction changes:

pytest --rerunner-record
rerunner verify tests/fixtures/agent_run.jsonl

Inspect the resulting files before committing them. They must contain only sanitized, synthetic data; never put API keys, tokens, customer data, or other secrets into a cassette or a cache.

2. Add a replay workflow

Save this as .github/workflows/replay.yml:

name: Replay agent tests

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  replay:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip

      - name: Install Rerunner and test dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install -e ".[dev]"

      - name: Cache recorded cassettes
        uses: actions/cache@v4
        with:
          path: tests/fixtures/**/*.jsonl
          key: rerunner-cassettes-v1-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('tests/fixtures/**/*.jsonl') }}
          restore-keys: |
            rerunner-cassettes-v1-${{ runner.os }}-py${{ matrix.python-version }}-

      - name: Run replay-only tests
        run: pytest

The default pytest mode is replay-only. The workflow therefore does not need an API key and does not call a model. The cache key changes when a cassette changes, while restore-keys can reuse the latest compatible cassette cache from the same Python version.

GitHub Actions can restore a cache from the default branch for a pull request, but a pull request from a fork may not be allowed to save a new cache. Seed the default-branch cache with reviewed cassettes, or commit the sanitized cassettes needed by the tests so a cache miss remains deterministic.

Security notes

Use pull_request for untrusted contributions. Do not switch this workflow to pull_request_target just to obtain write access to caches or secrets. Caches can be readable by people who can open pull requests, so treat them as public artifacts and review every cassette before it is cached. See GitHub's dependency caching reference for cache scope and pull-request restrictions.