Skip to content

Split Lint & Repo Gates into parallel lanes behind an aggregator, the way TypeScript Type Check already is: 193 serial steps are 28 minutes of every PR's critical path and one step is 41% of them #19686

Closed as duplicate

Description

@objectstack-fleet

The measurement

Lint & Repo Gates is the longest job in this repository's CI and it is the critical path: every other job finishes in 12 minutes or less and they all run in parallel with it.

reading value
Lint & Repo Gates duration 28 minutes
steps in it 193, all sequential
longest single step, PM dispatch-gates self-test 692 s, 41% of the job
next four steps 116 s, 100 s, 72 s, 61 s
PRs merged in the trailing 7 days 633 (about 90 a day)
cost of the PR leg alone about 295 compute-hours a week

Taken at head dc82eaf of #19642, run of 2026-09-22T05:47Z.

⛔ This card does not propose removing, weakening or skipping a single check. Measured on the same window, the PR leg of this workflow fails on 12 of 100 runs across 8 distinct branches, so it is catching real defects at a rate of roughly eleven a day. The job's CONTENT earns its place. Its SHAPE does not: 193 steps run one after another when almost none of them depend on the one before.

Why this is the cheap change

Three of the four other heavy jobs in this repository are already split, and one of them carries the exact pattern this card asks for. Type Check is four separate jobs — typecheck-source-gates, typecheck-workspace, typecheck-debt, typecheck-consumers — plus a fifth job named TypeScript Type Check whose whole body is needs: on the other four. That fifth job is the one the branch protection requires; it takes zero minutes and stands in for the lanes behind it.

That solves the hazard this card would otherwise carry. Lint & Repo Gates is one of the seven required contexts, and AGENTS.md is explicit that a required context is matched by check-run NAME, so a rename detaches its gate silently. The aggregator pattern keeps the name: the lanes get new names, and a job still called Lint & Repo Gates waits on them. No branch-protection setting changes, and nothing can ride through green as an advisory check-run.

The pattern also ships its own anti-drift control, which this card must adopt rather than reinvent: the aggregator declares a ROSTER in real YAML, scripts/check-aggregator-roster.mjs asserts statically that the roster equals the job's needs: in both directions, and the aggregate re-checks the same equality at run time against toJSON(needs). Without it, a lane added and forgotten rides green behind a context that appears required and blocks nothing.

What to do

  1. Group the 193 steps into lanes by what they read, not by duration. PM dispatch-gates self-test (692 s) belongs alone or with the other PM-tooling self-tests: it re-spawns its own CLI as a child process many times against the real checkout and builds temporary git repositories, so it shares almost nothing with the source-scanning gates.
  2. Give each lane its own job and its own needs:-free setup, exactly as the typecheck-* lanes do.
  3. Keep a job named exactly Lint & Repo Gates whose body is the roster declaration plus the verdict step.
  4. Wire the roster into scripts/check-aggregator-roster.mjs so the new aggregate is held to the same equality as the existing one.

Acceptance

  • The seven required contexts are unchanged by name; branch protection is not edited.
  • Every step that runs today still runs, on the same triggers. ⛔ Nothing is dropped, skipped or moved behind a paths filter as part of this card.
  • check-aggregator-roster.mjs covers the new aggregate, and a lane added without a roster entry is red.
  • Wall-clock for the aggregate measured on a real PR is reported in the PR body against the 28-minute reading above. The expected landing zone is the longest single lane, which is bounded below by the 692 s step until that step is separately optimised.
  • ⛔ Out of scope: making dispatch-gates.mjs's self-test faster. Its cost is real and has a documented reason (its companion gate's header explains why a fixture cannot prove the point); this card only stops it from holding 192 other steps behind it.

Activity

  1. objectstack-fleet commented on Sep 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: closed duplicate at first touch by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ), 2026-09-22T20:24Z.

    Path: none | 缺项 | none | 查重并卡

    Duplicate of #19189 — open, tooling · priority:p1 · pm:queue · domain:devx, unassigned. Same FILE (.github/workflows/lint.yml), same MECHANISM (that job's serial duration being the merge-queue critical path). The charter: 「同文件同机制 = 同一发现…证据搬先卡,后卡关 duplicate_of」, and the 在飞 exception does not apply because #19189 has no assignee.

    ⭐ The evidence here is better than the evidence there, and that is why this is a merge rather than a discard: this card carries the measured breakdown — the single step at ~41% of a ~28-minute job, the weekly compute figure, and the aggregator precedent that already exists in the same file for the typecheck lanes. That evidence belongs on #19189 and is the reason its p1 grade is right.

    ⚠️ ⛔ Not merged with #19687 (same file, different mechanism — the leg's catch rate). 同族异缺陷, 互链排同批.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions