Skip to content

ci: scope every expensive job to a capability vector — paths decide, labels override #1152

Description

@rickylabs

Summary

Every expensive CI job except the two scaffold tiers runs unconditionally on every pull request.
check-test (~7 min) and quality (~2 min) have no job condition at all, so an RFC or a
one-file documentation change pays the same toolchain bill as a change to packages/service.

The fix is not more workflow YAML. .github/scripts/ci-classify-changes.ts already exists, is
pure, unit-tested, and conservative by design — "an unrecognised path forces the gate to run. It
NEVER skips because it failed to classify."
It just answers only two questions, for one workflow.
Promote it to a capability vector that every expensive job reads.

Measured — last 20 closed PRs

Job Gate today Total Ran on
check-test none — always runs ~138 min 20/20
scaffold-runtime (aspire + docker + postgres) classifier ~128 min 15/20
scaffold-static (deno-only) classifier ~46 min 15/20
desktop-native-linux declared, not honored (tracked separately) ~41 min 20/20
quality none — always runs ~38 min 20/20

390 min / 20 PRs ≈ 19.5 min per PR.

Five of the twenty touched no shipping code: #1123 (RFC), #1086, #1063, #1053, and #1055
which changed one file under .agents/. The classifier correctly skipped both scaffold tiers on
all five. Each still burned ~10 minutes on check-test + quality + desktop-native-linux,
because those jobs never consult it.

That is the RFC gap precisely located: the docs-only skip does fire for RFCs — just not on the
jobs that cost the most.

Design — one classifier, many consumers

Emit a capability vector once per PR, consumed by ci.yml and e2e-cli.yml alike:

Output Consumers Path signal (starting set)
needs_deno check-test, quality packages/**, plugins/**, apps/**, deno.json*, deno.lock
needs_docker docker tier of scaffold-runtime database/queue/cache packages, Aspire helper generation, Prisma schema, plugins/**
needs_desktop desktop-native-linux deploy / desktop / packaging paths
needs_docs docs accuracy, link integrity docs/**
needs_surface surface-diff already path-filtered; fold in for one decision point

Invert the docker default

scaffold-static (deno-only) and scaffold-runtime (aspire + docker + postgres) already form two
tiers. What is missing is that any packages/** change escalates to the docker tier. Docker
should be the exception, reached on needs_docker, with a deno-only runtime tier as the default —
this is the single largest line in the table.

Paths are the mechanism; labels are the override — ratified 2026-08-03

Keep exactly the three labels that exist: ci:full, ci:skip-scaffold, ci:skip-e2e.

Add no per-technology labels (postgres, prisma, redis, docker, …). Deriving routing from
labels moves the decision into GitHub's label UI, where it cannot be unit-tested and every author
must remember to apply it — and the label set grows without bound until it is itself unmaintainable.
The classifier is tested code; labels are untested configuration. Prefer the tested one. A human who
knows better reaches for ci:full; nobody has to remember to tag postgres.

Two risks to hold

  1. A skipped job must still report, or it stops being required. scaffold-static already
    solves this — it starts and reports success with a skipped-by-policy step. Reuse that pattern for
    check-test and quality; do not invent a second one, and do not simply drop the jobs.
  2. needs_docker's path set is the dangerous one. Too narrow and a wiring regression ships.
    Start it deliberately wide and tighten only against observed green history — the same
    "additive until observed green" discipline e2e-cli.yml already documents for the runtime lane.

Acceptance

  • ci-classify-changes.ts emits the capability vector above, unit-tested per output
  • check-test and quality consult needs_deno and report skipped-by-policy when it is false
  • scaffold-runtime splits into a deno-only default tier and a docker tier gated on
    needs_docker
  • No second routing table exists in workflow YAML — jobs read classifier outputs only
  • The label set is unchanged: ci:full, ci:skip-scaffold, ci:skip-e2e, and no others
  • Conservative default preserved: an unrecognised path still forces the gate to run
  • Measured before/after on a comparable PR sample, recorded on this issue

Provenance

Owner observation 2026-08-03: the Actions queue grows with parallelised agent PRs and most PRs run
CI unrelated to their scope. Measured over the last 20 closed PRs. The
desktop-native-linux wiring defect found in the same audit is tracked separately as a discrete
fix that can land ahead of this redesign.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions