ci: PR lint gates fail only for the PR's own findings (no bead)
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 nowAgentPanePastChatfrom #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.swiftthat 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-lintfailed on the baseAgentPanePastChatviolation, 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