Skip to content

Master CI gatekeeper: a green PR does not predict a green master #281

Description

@pftg

Highest priority. Master CI was red for six consecutive commits while every PR showed green, and a release was nearly cut on it.

Why PR-green cannot predict master-green

.github/workflows/test.yml:104 — the full Test Ruby & Rails matrix is deliberately kept off pull requests:

# Cost-intentional: full matrix stays off PRs by default (free-tier
# Actions minutes). Runs on master pushes, manual dispatch, the weekly
# scheduled drift check, and PRs opted in via the 'full-ci' label.
if: >
  github.ref == 'refs/heads/master' ||
  github.event_name == 'workflow_dispatch' ||
  github.event_name == 'schedule' ||
  contains(github.event.pull_request.labels.*.name, 'full-ci')

That trade-off is reasonable. The consequence is not obvious: the job that breaks master is a job that never ran on the PR, so PR checks are green by construction and every merge is a blind push.

What actually broke (tracked separately)

Subprocess probes chdir into a temp directory — correct, and the documented rule — while inheriting the workflow's relative BUNDLE_GEMFILE: gemfiles/rails81_gems.rb (test.yml:30), which then fails to resolve:

/tmp/d20260824-2715-vtghif/gemfiles/rails81_gems.rb not found (Bundler::GemfileNotFound)

Invisible locally, because bundle exec normalises BUNDLE_GEMFILE to an absolute path before spawning. The documented rule is chdir out and scrub RUBYOPT/BUNDLE_GEMFILE — the chdir landed, the scrub did not. Fix in flight on fix/probe-env-under-ci.

The gatekeeper

  • Process rule recorded in the loop protocol: after every merge, check gh run list --branch master --limit 1. A red master blocks the next pull. cancelled is not a pass — it means a later push superseded the run.
  • Live monitor watching master's latest run, reporting every terminal conclusion (success, failure, cancelled) rather than only failures — silence must not look like success.
  • Label rule: any PR touching subprocess probes, env handling, gemfiles, or CI itself gets the full-ci label before merge, so the matrix runs where it can still block. Document this in CONTRIBUTING.md next to the label.
  • Decide whether full-ci should be automatic for paths that have historically broken master — test/**, gemfiles/**, .github/**. A paths-based label bot, or simply making the matrix run on PRs that touch those paths. Costs minutes; buys the guarantee that PR-green means something.
  • Branch protection should require the master-push matrix once its check names settle (they change with 2.1: delete the legacy namespace trees and the driver abstraction #249, 2 dimensions → 1). Until then the rule above is the only gate.

The general lesson, for the record

A CI configuration that runs different jobs on PRs than on master is a configuration where green means two different things depending on where you read it. That is defensible for cost, but it must be stated where people merge, not only in a comment inside the workflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions