A workflow is YAML and the runner is empty every time

A long-lived CI server accumulates state that the pipeline quietly depends on — a global package, a cached credential, a directory somebody created in 2017. A hosted runner has none of it, every time.

name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '7.4'
          extensions: pdo_mysql, redis
          coverage: none
      - run: composer install --no-interaction --prefer-dist
      - run: vendor/bin/phpunit

coverage: none is worth setting explicitly because the default installs Xdebug, which triples the runtime of every test job for a report nobody asked for. Pinning the runner image to ubuntu-20.04 rather than ubuntu-latest is the other habit: latest moves, and a build that broke because the image changed underneath is a bad morning with no commit to blame. The freshness is the whole value — a pipeline that assumes nothing is a pipeline that works on a new machine.