mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #93] [MERGED] feat(groom): GROOM_CONFIG — tune the groom loop from a repo variable (BE-5227) #102
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#102
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/93
Author: @mattmillerai
Created: 7/30/2026
Status: ✅ Merged
Merged: 7/31/2026
Merged by: @mattmillerai
Base:
main← Head:matt/groom-config-and-dedup📝 Commits (4)
4aba6dffeat(groom): GROOM_CONFIG resolution layer for operational knobs (BE-5227)f9fe00dfeat(groom): wire GROOM_CONFIG through the reusable + add a paths knob (BE-5227)9ac79d8fix(groom): harden the GROOM_CONFIG coercers against the fail-open holes (BE-5227)a0ae5dbrefactor(groom): drop the competing paths knob — #83 owns path scoping📊 Changes
3 files changed (+870 additions, -21 deletions)
View changed files
➕
.github/groom/config.py(+440 -0)➕
.github/groom/tests/test_config.py(+309 -0)📝
.github/workflows/groom.yml(+121 -21)📄 Description
ELI-5
Groom has about a dozen dials — how often it runs, how many PRs it may open, what it looks at, whether it files anything at all. Today every one of those lives in a workflow file inside each consuming repo, so turning a dial means opening a PR in that repo and waiting for review. Turning a dial should not need a code review.
This reads those dials from a single JSON repo variable instead, layered on top of whatever the workflow file already says. A repo that sets nothing keeps behaving exactly as it does today. A repo that sets the variable gets its new values on the next run, with no PR.
What is here
Both halves — the resolver and the
groom.ymlwiring that consumes it..github/groom/config.py— resolves the knobs. Precedence, lowest first: reusable defaults < callerwith:<vars.GROOM_CONFIG< theconfiginput..github/groom/tests/test_config.py— 50 tests. Full groom suite green on the CI matrix..github/workflows/groom.yml— theconfiginput, theresolvestep ingate, and theresolvedoutput threaded through the downstream jobs. Every consuming job now reads its knobs viafromJSON(needs.gate.outputs.resolved).<knob>instead ofinputs.<knob>, so the merge happens in exactly one place.Merging this does change behavior for existing callers — every knob read is rerouted through the resolver. A caller that sets no variable resolves to its current
with:values, so the intended net effect is nil, but the code path is new and that is what review should be aimed at.Design notes worth reviewing
There is deliberately no
workflow_dispatchlayer. A reusable workflow cannot see the caller's dispatch inputs: inside a called workflowinputsis that workflow's own inputs, andgithub.event.inputsis the caller's payload only for the caller's own jobs. A caller forwarding a dispatch input does it per-knob (max_prs: ${{ github.event.inputs.max_prs || '3' }}), which lands in the baseline below the variable. Consequence worth stating plainly: onceGROOM_CONFIGsets a knob, that variable wins and a per-knob dispatch override of the same knob silently stops taking effect. A caller that wants one-run overrides to keep winning forwards a JSON blob intoconfig, which ranks above the variable.vars.GROOM_CONFIGis read inside the reusable, not passed in. Thevarscontext in a called workflow resolves against the caller's repository, so every consumer inherits variable-driven config with nowith:change — only a pin bump, which thebump-groom-callersfleet already automates. (Verified in-org:assign-reviewers.ymlreadsvars.REVIEWER_LOAD_CAP, set oncloudand not on this repo.)It cannot touch the security boundary. An Actions variable is editable by anyone with repo write — the same principals who could edit the workflow file — but editing it bypasses PR review and leaves no diff on a branch. So
builder,pr_size_limit,sink,bot_app_id,workflows_refandconfigitself are refused with a loud warning rather than silently dropped.configis in that list so a variable cannot nominate its own higher-precedence layer. There is a test asserting every one of them is unreachable, so this cannot regress quietly.Fail-open, everywhere. Malformed JSON warns and falls back to the layer below. A typo in a variable must not turn a daily scheduled sweep red until somebody notices. Each individual drop is warned about with the key named.
Refuses to guess.
"yes",1and"on"are all rejected for a boolean rather than coerced — reading"yes"as false would quietly re-enable live filing. Same forboolwhere a number is expected, sinceboolis anintsubclass in Python.scope_labelis settable but announces itself. It is field 2 of every dedup signature, so changing it re-keys the entire ledger and groom re-proposes its whole backlog once. Legitimate for a genuine re-scope, never something you want by accident, so it warns every time.One output, not eleven. Job outputs are strings, and two knobs are multi-line prose — eleven separate outputs would mean heredoc delimiter plumbing per knob plus an
env:line in every consuming job. The resolved set is emitted as one compact JSON line.max_prs/interval_days/cadencestay strings on purpose. Each already has a downstream parser that owns clamping and fail-open degradation (build_selectandinterval.py). Re-clamping here would create two places to disagree.Known leftover, flagged for this review
config.pystill exposes a--dispatch-jsonargument, andresolve()still takes adispatch_rawlayer labelledworkflow_dispatch inputs. It is a vestige of the original design and the shipped wiring never passes it —groom.ymlinstead makes two--config-jsonpasses to order variable-then-config. It is inert (defaults to""), but the label names a layer that architecturally cannot exist, which is exactly the sort of thing that misleads the next reader. Worth deciding in review whether to drop it or keep it as the seam a non-reusable caller could use.Not in this PR
paths— an earlier revision carried a competing path-scoping knob. It was dropped rather than merged into a second implementation: #83 owns path scoping via apathinput backed byscope.py, and it also teachesinterval.pynot to reset the cadence when scope narrows. There is a test assertingpathsis not a config knob. Once #83 lands, addingpathto_OPERATIONAL_KEYSand to theDEFAULTSblock makes it variable-tunable with no further wiring.setup_commandinput, so the builder job has a toolchain and can verify its patch instead of bailing.Testing
python3 -m unittest discover -s .github/groom/tests -p "test_*.py"— green locally on 3.14, and the CI matrix job (3.12) is green on this PR, which is the real check for version skew. Nothing in either file uses post-3.12 syntax.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.