mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #43] [MERGED] feat(groom): durable dedup/rejection ledger backed by GitHub issue state (BE-3874) #53
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#53
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/43
Author: @mattmillerai
Created: 7/21/2026
Status: ✅ Merged
Merged: 7/21/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-3874-groom-rejection-ledger📝 Commits (3)
5c24e7afeat(groom): durable dedup/rejection ledger backed by GitHub issue state (BE-3874)97ade96fix(groom): harden ledger marker + batch dedup per review (BE-3874)e612423Merge remote-tracking branch 'origin/main' into matt/be-3874-groom-rejection-ledger📊 Changes
5 files changed (+856 additions, -1 deletions)
View changed files
📝
.github/groom/README.md(+73 -1)➕
.github/groom/ledger.py(+422 -0)➕
.github/groom/tests/test_ledger.py(+314 -0)➕
.github/workflows/test-groom-scripts.yml(+39 -0)📝
AGENTS.md(+8 -0)📄 Description
ELI-5
The "groom" janitor bot files GitHub issues for code cleanups it spots. In CI it
has no memory — every scheduled run it wakes up with a blank brain. So it
would file the same cleanup again next week, and — worse — re-file things a
human already looked at and said "no thanks" to. That's how you get everyone to
mute and disable the bot.
This PR gives it a memory that survives across runs, without any new database
or secret: it just uses the GitHub issues themselves as the memory. Before
filing, it lists the cleanup issues it already opened and skips any it's seen —
including ones a human closed as "won't fix" (those stay suppressed forever).
What
.github/groom/ledger.py— a durable dedup/rejection ledger keyed on(repo, finding_signature) -> {filed | rejected | superseded}, built by readinglive GitHub issue state.
groomissuefiledfiledrejectedgroom-rejectedlabel (open or closed)rejectedgroom-supersededlabelsupersededgroomissue carries itunknownFiling embeds the verifier's opaque signature as an invisible HTML-comment
marker (
<!-- groom-signature: … -->) in the issue body; the next run recoversit. Pure logic (marker round-trip, classification, ledger build, candidate
partition) is separated from the one thin
gh apiI/O shell, so it's fullyunit-tested with no network (
.github/groom/tests/, 29 cases, wired into CI viatest-groom-scripts.yml).CLI the groom workflow calls right before filing:
Why this store (design decision)
The ticket asked to prefer the GitHub-native option that needs no net-new
secret. Issue state wins on all three acceptance points at once: it needs no
separate store, the run's built-in
GITHUB_TOKENalready reads issues (no newsecret), and it's inherently auditable (the record is the issues). It also
captures human rejection for free — a maintainer closing an issue as "not
planned" is the rejection signal, no extra tracking needed. I considered a
committed
.ledger.jsonbut rejected it: it duplicates state that already livesin the issues and adds bot-commit noise/merge churn on every run.
Acceptance criteria
existing
groomissue for a signature makes itknown;should_fileis trueonly for
unknown. (AcceptanceScenarioTest,LedgerDecisionTest.)not_plannedor thegroom-rejectedlabel →rejected; read live every run, so it never expires.gh apiwith the run'sGITHUB_TOKEN.Self-review notes / judgment calls
workflow). The memory only works if the step that opens an issue applies
the
groomlabel and appendssignature_marker(sig)to the body. This isthe correct seam — a ledger can't remember what the filer didn't record — and
it's documented in the module docstring + README. The filing step is Phase 1/2
of BE-3870; this PR is the standalone hard piece it will call.
debt). Deliberate anti-annoyance default matching the acceptance wording
("filed in run N not re-filed in N+1"). If re-detecting regressions is wanted
later, treat
completedasunknown— it's a one-constant change.supersededis a defined hook with no producer in this PR — thegroom-supersededlabel is classified if present, part of the ticket's{filed|rejected|superseded}model; nothing here sets it yet.gh apierrors,fetch_groom_issuesraisesrather than returning an empty ledger — an empty ledger would re-file
everything (the exact spam this prevents).
supported" path; the
invalidbucket is defensive routing of malformed(signature-less) input, surfaced in output — not a user-facing denial. Trigger
does not fire.
changed (Chesterton's Fence clean). AGENTS.md integrity checker still passes.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.