The workflow model

hypervise update-config [--physical-cpus K] [--recording | --no-recording] [--shards N]

Rules

  1. Every job starts recorded, on 1 physical CPU, as 1 shard. Anything else is an explicit update-config step.
  2. Recording needs 1 physical CPU. A recorded machine runs on one core, which is what makes it deterministic. --recording with more than one CPU is an error.
  3. Shards are for good. --shards N splits the machine into N exact copies, and every later run: step runs in every copy. There is no way back to one machine (half-done work can't be merged). Later update-config calls apply to every shard. One split per job.
  4. Physical CPUs are host cores. The VM always has the runner's vCPUs. --physical-cpus K runs them on K cores and keeps K online, so nproc is K.

What a step sees

Variable Meaning
HV_SHARD_INDEX this shard, from 0 (0 before a split)
HV_SHARD_COUNT number of shards (1 before a split)
HV_RECORDED 1 while recorded

After a split

  • run: steps run in every shard. The step's log shows one group per shard, and the step fails if any shard failed.

  • uses: steps run once, in the machine that keeps the GitHub runner (its state from before the split). To hand files to such a step, gather them first:

    - run: hypervise gather reports/junit.xml
    - uses: actions/upload-artifact@v4
      with: {name: reports, path: shard-*/reports/}
    
  • Shards have no network. Fetch what the tests need before splitting: cargo fetch, npm ci, go mod download, docker pull.

Costs

Change Time
a split about 5 s
switching between recorded and unrecorded about 8 s
changing the CPU count while unrecorded about 8 s (a later version will do it in place)