mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #289] [MERGED] feat(linear-ticket): add an exempt-paths input so config-only PRs skip the gate #290
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#290
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/289
Author: @mattmillerai
Created: 9/15/2026
Status: ✅ Merged
Merged: 9/15/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-14482-linear-ticket-exempt-paths📝 Commits (2)
d0ac747feat(linear-ticket): add anexempt-pathsinput so config-only PRs skip the gate1e70455fix(linear-ticket): fail the path exemption closed on a changed-file list that does not match the PR (BE-14482)📊 Changes
8 files changed (+794 additions, -13 deletions)
View changed files
📝
.github/workflows/linear-ticket.yml(+22 -2)📝
README.md(+1 -1)📝
docs/callers/linear-ticket.md(+78 -1)📝
scripts/linear-ticket/README.md(+30 -0)📝
scripts/linear-ticket/lib.py(+119 -0)📝
scripts/linear-ticket/tests/test_lib.py(+125 -4)📝
scripts/linear-ticket/tests/test_validate.py(+273 -3)📝
scripts/linear-ticket/validate.py(+146 -2)📄 Description
ELI-5
The "did you link a Linear ticket?" check has only two escape hatches today: a label someone applies by hand to one PR, or a list of author accounts that skip the check forever. Neither one fits a repo that gets a steady drip of ticket-less config-only chores — launch-gate flips, version syncs — opened by half a dozen different people. This adds a third hatch: give the workflow a list of path patterns, and a PR whose changed files all match is waived automatically.
The interesting part is where the exemption lives. The obvious fix — a
paths-ignore:on the caller's signal workflow — is a trap, becauseLinear ticketis a commit status that repos eventually mark required. Filtering the signal means no status is ever published, and a required context that is never published doesn't pass by omission: GitHub leaves it pending forever, blocking exactly the PRs you were trying to let through. So the exemption is decided inside the validator and exits through the existingfinish_exempt(), publishing a green status that names the reason.Description
New optional
exempt-pathsinput onlinear-ticket.yml, plus the validator logic, tests and docs behind it.paths-ignore. One config file plus one Go file is not exempt.GET /repos/{owner}/{repo}/pulls/{number}/files— GitHub-owned metadata, not the PR branch, so it stays inside the reusable's fork-safety constraints. Paginated at 100/page. A rename contributes both its new path and itsprevious_filename, so code cannot be moved into an exempted directory to buy the exemption.changed_files_unavailablered status rather than declaring a partial list fully matched. "Malformed" also covers a returned length that disagrees with the PR payload's ownchanged_filescount, and any entry that is not an object carrying string paths.**crosses/,*does not, everything else is literal,!negation is rejected. Every limitation under-matches, and under-matching leaves the ticket requirement in force — the safe direction...segment), the way a malformedteam-keysalready does.Judgment calls worth a reviewer's attention
changed_files_unavailableis a new failure category rather than reuse ofinfra_error.infra_error's copy says Linear could not be queried — a diagnosis this run never reached. The repo already treats that distinction as load-bearing (there is a test assertinginfra_error's status description must not read "no linked Linear issue"), so a fifth category with its own headline, guidance and advisory tail seemed more in keeping than borrowing copy that lies. It routes throughfinish_failand followsinfra_error's exit-code contract exactly.>=rather than>at the 3000-file cap. GitHub documents the maximum but not what it does past it, so a PR sitting exactly at the ceiling is indistinguishable from a truncated one and is refused too. Fails in the safe direction (red, not silently exempt).An unreadable file list reds a PR that might have a perfectly good linked ticket. This is the acceptance criteria's explicit instruction and I implemented it as specified, but it is worth naming the cost: in a repo that sets
exempt-paths, a >3000-file PR fails the check as an infrastructure error even if its ticket is linked and open. The alternative — falling through to the Linear query, since a >3000-file PR is definitionally not config-only — would be strictly friendlier, and is a defensible follow-up if the fail-closed posture ever bites. It affects opt-in callers only.Only the changed-file COUNT is logged, never the filenames. A git path may legally contain a newline, so echoing an author-controlled filename from this privileged job would let a branch forge
::error::workflow commands in the run log. The cost is that "why isn't my config PR exempt?" is less self-diagnosing from the log alone.Stricter than
normalize_team_keyson empty entries.team-keystolerates a strayBE,,ENG;exempt-pathsrejects it. The acceptance criteria call for it, and the rationale holds: this input is a merge-gate waiver, so it runs under exactly what the caller wrote or it does not run at all. Flagging it because it is a deliberate inconsistency with the neighbouring parser, not an oversight.Nothing stops
exempt-paths: **, which turns the gate off while still looking configured. I did not add a guard — a maintainer writing that is explicitly opting out, and the label/actor hatches have no equivalent paternalism — but the caller doc now carries a warning callout.Verification
No behavioural change reaches any existing caller: the input defaults to empty, and a test pins that an empty value never even looks up the changed files.
The negative-claim check applies to the 3000-file dead-end this diff adds, so I falsified it against the source rather than asserting it: GitHub's REST docs for the endpoint state verbatim "Responses include a maximum of 3000 files." They notably do not document overflow behaviour, which is the actual argument for failing closed — a partial read cannot be ruled out. Page size is 30 by default, hence the explicit
per_page=100.The matcher was also exercised by hand against realistic paths to confirm it does not leak across a sibling prefix (
infrastructure/dynamicconfig-backup/x.yamlandInfrastructure/dynamicconfig/x.yamlboth correctly do not matchinfrastructure/dynamicconfig/**).Residual
Comfy-Org/cloudcaller PR is not included. The acceptance criteria call for a second PR in that repo bumping the caller'suses:SHA andworkflows_ref:in lockstep and settingexempt-paths: infrastructure/dynamicconfig/**, verified by a config-only PR showing a greenLinear ticketwith the exemption reason. That is a different repository and is out of this PR's scope; it is also blocked until this merges, since both pins must point at a merged-upstream commit (the reusable's ancestry guard rejects anything else). Note the SHA half will happen on its own:bump-linear-ticket-callers.ymlwatches.github/workflows/linear-ticket.ymlandscripts/linear-ticket/**, both of which this touches, so the fleet opens the bump PR on merge — what still needs a human is adding theexempt-paths:line to that caller and confirming the green status on a real config-only PR. Nothing in this PR has been exercised against a live PR, in cloud or anywhere else; verification is the hermetic suite plus the by-hand matcher run above.finish_exempt()— which the ticket separately, and explicitly, requires this to reuse — deletes the marker comment rather than writing one, because the gate deliberately maintains exactly one comment and only for failures. Honouring the comment half would mean changingfinish_exempt()for the existingexempt-labelandexempt-actorspaths as well, putting a new comment on every Dependabot PR in every consumer repo. I took the reuse instruction as the stronger of the two and left the comment behaviour alone; the exemption reason is still on the PR, in the status description beside the check name. Reversing that call is a small change if a maintainer disagrees.require-open-issueis untouched, per the ticket's own out-of-scope list. The PR that surfaced this went red because a linked-but-Done ticket failsrequire-open-issue: true, not because no ticket was linked. A config-only PR is exempt under this change regardless of that setting, so the two are independent — but if the Done-ticket case deserves its own treatment, nothing here addresses it.Comfy-Org/cloudPR the ticket cites as evidence (the config-only PR stream used to establish the authorship spread, and the workflow run that published the red status) sits in a private repository I cannot read from this environment, so the authorship-spread claim that motivates a path exemption overexempt-actorsis taken from the ticket rather than re-measured. The corpus sizing the ticket did — ~15 recent config-only PRs — was likewise not re-swept, so I have no independent count of the portion this exemption would not cover.Provenance
python3 -m unittest discover -s scripts/linear-ticket/tests -p 'test_*.py': 116 passed, 0 failed (6 added this round covering the changed-file count mismatch in both directions, the rename case that proves the count is compared against entries rather than expanded paths, and the three malformed-entry shapes);python3 -m py_compile scripts/linear-ticket/{lib,validate}.py: clean;python3 .github/workflow-pins/check_workflow_pins.py: OK, 11 workflows, none with a defaulted ref;python3 .github/agents-md-integrity/check_agents_md.py --root .: passed (2 pre-existing warnings, untouched by this diff). Two claims in the review round were checked empirically rather than asserted: the new count-mismatch guard was falsified for false positives against ten real PRs in this repo (changed_filesvs. the files-endpoint length agreed 10/10, including a merge commit and PRs with renames), and the strengthened startup-order test was mutation-checked (it fails whenmain()is reordered to parse the event before validatingEXEMPT_PATHS, and passes when restored). The--slurpreview finding was refuted by runninggh api --paginateatper_page=2against this PR: four pages decode to one flat 8-element list, notNone.## Residual— the companionComfy-Org/cloudcaller PR (different repository, and blocked on this merging) and the marker comment on an exempt PR (incompatible with thefinish_exempt()reuse the same ticket requires). No deviation in the review round: four of the five review findings were applied as filed; the--slurpone was declined on the empirical evidence above rather than changed.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.