ci: PR lint gates fail only for the PR's own findings (no bead)

Merged
TE
1mo ago
+361-23

Summary

package-conventions-lint (with the ios-tests gate that mirrors it) and cmux-next crash-class rules lint the whole tree. When a violation reaches feat-cmux-next, every PR that touches Packages/ fails on it until someone fixes base forward. Over the last 24h of feat-cmux-next PR runs:

  • package-conventions-lint failed 84 of 376 runs across 45 PRs. The cause was three base violations in a row: TaskContext, AgentPresenceColor, and now AgentPanePastChat from #19518.
  • ios-tests failed 86 times, the same failures repeated by its gate.
  • crash-class failed 45 of 330 runs across 26 PRs. The cause was one base violation in HistoryService+Daemon.swift that stayed for 8.5h.

The always-red base debt is also why PRs merge on their own red. #19518 merged with its own lint failing.

On a PR's merge commit, the gates now fail only for:

  • findings in files the PR changes (the merge commit's first parent compared with HEAD), or
  • any finding, when the PR changes the lint's code (its scripts, the gate, or the workflow), or
  • the paths named by a baseline or ratchet line the PR adds or removes.

Findings elsewhere print as ::warning annotations ("On the base branch, not in this PR") that name the file. Then the gate passes. Base pushes and dispatches keep the whole-tree verdict, so crash-class still goes red on the push that lands a violation.

The scoped rule is applied to the full lint's output, not through --files-from. That flag drops each rule's directory scopes, so iOS-only rules would apply to macOS packages. The lint's failure still stands when it did not finish cleanly: a Python traceback, no finding the gate can read, or a crash-class summary count that differs from the findings read. Without this, a lint that crashed on the PR's file after printing a base finding would pass.

merge_group runs keep the whole-tree verdict too. One known gap: a PR change that makes an unchanged file violate a rule shows only as a warning on that PR. The next base push run (crash-class) or the next PR to touch that file catches it.

Testing

scripts/tests/test_pr_scoped_lint_gates.py builds fixture repos whose HEAD is a PR merge commit, then runs the real lints through the gate scripts. It runs in the package-conventions-lint job.

  • Red (917165bf8414, job bodies moved into the gate scripts unchanged): FAILED (failures=3). The three failures are the cases where the PR is beside a base violation it doesn't touch.
  • Green (f7a707ddfaac): Ran 10 tests ... OK. These cover adding a violation, editing a violating file, changing the lint rules, and running without PR scope.
  • Review follow-up, red (f1f1a98ba548): FAILED (failures=2). One is a crash-class lint that crashes on the PR's file after printing a base finding. The other is a PR that pays down baselined debt beside unrelated base debt.
  • Review follow-up, green (7c9c307b6893): Ran 13 tests ... OK. With both crash guards removed, the crash case fails, so it covers them.
  • In CI on f7a707ddfaac, package-conventions-lint failed on the base AgentPanePastChat violation, as intended: this PR changes the lint, so it owns every finding. That violation is fixed on base since #19535.

actionlint passes. Locally, the workflow guard tests that read these workflows show the same 15 pre-existing failures on base and on this branch.

Changelog

none

🤖 Generated with Claude Code