Kai Ole Hartwig
5 min read
By

You Can't Blindly Bundle CI Jobs

Consolidating jobs to save runner slots is tempting on a small pool. It also silently throws away per-job behaviour you didn't know you relied on. Part 2 of "The Ops Log."

TL;DR

TL;DR: Merging N jobs into one drops per-job allow_failure, per-job artifacts/JUnit, and red/green-per-check in the UI. Build the merged job additively and validate it differentially — run new-vs-old on a real repo and compare verdicts — before you swap anything fleet-wide.

What happened

The lint stage ran eight separate jobs on the same PHP image: composer normalize / validate / psr, php-lint, json, yaml, typoscript, commit-msg. Eight jobs means eight pulls of the same image and eight runner slots. On a spot pool that caps at sixteen concurrent, that is the bottleneck. Fold them into one job → one pull, one slot. Easy win, right?

Here's what "one job" quietly costs you, none of which shows up until it bites a specific repo:

So the merged job needs to re-implement the semantics it's collapsing. Change-gating moves inside the script (each check runs only if files it cares about changed — fail-safe: if the diff base can't be resolved, run everything rather than skip and go falsely green). And allow_failure comes back as a per-check soft-checks input:

soft-checks — allow_failure, per check

 

run() { # $1=key $2=label $3=fn
  if ! ( set -eu; "$3" ); then
    case " $SOFT " in
      *" $1 "*) echo "⚠ $2 failed — soft, not failing the job" ;;
      *)        echo "✗ failed: $2"; FAILED=1 ;;
    esac
  fi
}

 

Then — and this is the actual point — don't trust it because the YAML lints and bash -n passes. Validate differentially: ship the new job additively (old eight untouched), add it to one real consumer alongside the originals, and compare verdicts check-by-check on the same commit.

That one exercise caught three things no amount of reading would have:

differential run — first consumer

 

lint:composer:normalize   failed   (allow_failure — tolerated)
lint:composer:validate    success
lint:composer:psr-verify  success
lint:php:all              failed   ← why?
  ── composer normalize   ⚠ soft   soft-checks works ✓
  ── composer validate    ok
  ── json lint            ✗       jsonlint binary missing → see post 01

None of those are happy-path. A green pipeline on the new job would have "passed" while proving nothing about equivalence. The conclusion I keep re-learning: the merged abstraction stays an opt-in for repos that pass cleanly, and the fleet-wide swap becomes a planned migration — because every consumer that overrode a per-job setting has to be mapped, not assumed.

The lesson

A green pipeline proves the happy path, not that your new thing is equivalent to the old thing. Differential validation on a real consumer is the cheapest insurance you'll ever buy before a fleet-wide change.

Frequently asked questions

Why migrate per consumer instead of fleet-wide all at once?+

Because every consumer that individually overrode a job setting (like allow_failure) has to be mapped — a blind fleet-wide swap would lose exactly those customizations.

Is a green test run on one repo proof enough?+

No — a single green run only shows the happy path. Only comparing verdict-by-verdict against the old jobs on the same commit surfaces gaps like missing allow_failure tolerance.

When is job consolidation still worth it?+

When the runner pool is genuinely the bottleneck (here: 16 concurrent slots) and the semantics you'd lose — per-job allow_failure, artifacts, parallelism — get deliberately rebuilt in the merged job instead of silently disappearing.

Conclusion

Job consolidation saves runner slots, but only free on paper. Every per-job setting that disappears has to be deliberately rebuilt in the merged job — and the only reliable proof that it worked is a differential run against a real consumer, not a green YAML lint.

I build and validate your CI pipeline consolidation — additively, differentially tested, with no silent behavior loss.

From job analysis (which per-job settings actually exist?) through additive migration to differential validation against real consumers, before anything gets swapped fleet-wide.

Platform operations, not consulting on paper: I build and maintain your CI/CD pipelines on an ongoing basis.

Book a call →

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.