claim-sweep: the pr-sweep sibling that owns claim staleness (#286) - #287
Merged
Conversation
`claim-ticket.yml`'s header already named this file as the fix: "Nothing expires stale claims yet; if that starts to hurt, it is a scheduled sweep in the board-parity family, not more logic here." Measured 2026-08-29 against the live API: 114 claims org-wide sit on finished work, 92 of them in this repo and `.github-private`. The claim doors write the `claimed` label and nothing removes it, so the count only grows -- claims.bounded.tools renders it as "114 finished, still labelled" and says outright that it only grows until someone drains it. Ten further claims in `.github-private` are open with no linked PR and no session behind them, several dating to 08-07; those are the ones with real exclusion cost, because a claimed issue reads as taken to every session that looks. Built as the pr-sweep sibling: weekly cron (a real run, per the #713 standing decision) plus dispatch with dry-run default. Per claimed issue: exempt a standing claim; release if the issue is closed, whatever closed it; release if open, PR-less and untouched for stale_days (default 14 -- claims are leases on attention, shorter than the 30-day PR window); keep everything else with the reason printed. Release is label removal, an unassign and one comment naming the run -- exactly what every claim record already tells its reader to do, made mechanical. THE EXEMPTION IS LOAD-BEARING. The six machine-lane standing claims are open, PR-less and never refresh, which is precisely the shape the staleness rule deletes. `.github-private`#731 predicts this sweep by name: "a standing claim never refreshes, so any wall-clock expiry (#717) would expire it first." Without the exemption the first cron breaks every lane in the org, and for the three fan-out lanes in ~86 repos at once. So two guards, because one was not enough. The exemption keys on a label rather than on parsing a `machine/*` claimant out of hand-written comment prose, which is not a checkable interface. And the staleness rule refuses to run at all in a repo where that label does not exist, saying why: if no standing claim there can be marked, rule 3 is not safe to apply. The closed-issue rule is unaffected either way, since a standing claim is open by construction -- so the 92-issue drain works before the label rollout and the dangerous half stays off until it is done. Unassign failure is reported, never fatal: the label is the half pr-claim reads, so its removal is what ends the claim; the assignee is a human-facing projection and must not be load-bearing. The release comment is built with `jq -nc` rather than inline prose -- this repo's workflow ratchet refuses content at column 0 in a run: block, and caught exactly that defect in the first draft. Rollout steps that are not in this diff are checkboxes on #286: create the `standing-claim` label in both repos and apply it to the six standing claims, and register the `claim-sweep` broker entry. Until the broker entry exists, cross-repo runs fail at mint naming the gap -- the intended arrival state; same-repo runs work on the workflow's own token. Tests: 254 root, 207 .claude, 93 workflow ratchets, all passing. Closes #286 Co-Authored-By: Claude <noreply@anthropic.com>
bdelanghe
marked this pull request as ready for review
August 29, 2026 16:48
This was referenced Aug 29, 2026
Closed
bdelanghe
pushed a commit
that referenced
this pull request
Aug 29, 2026
#287 was squash-merged, so main carries one squashed commit while this branch still had the pre-squash original plus the fix on top. The histories diverged and GitHub reported the PR as `dirty` -- an add/add conflict on claim-sweep.yml, both sides having "created" the file. Resolved toward this branch: main's copy is the pre-fix version whose release path uses `gh api -d`, which is not a flag and is exactly what run 2 died on. Taking main's side would have re-landed the defect. Verified after resolving: no conflict markers, no `gh api ... -d` anywhere in the file, four `--input -` call sites, and 254 root + 207 .claude + 93 workflow ratchets all passing. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #286
claim-ticket.yml's own header already named this file as the fix:The measured problem
Against the live API on 2026-08-29:
.github-private).github-private#373, #372, #314)The claim doors write the
claimedlabel and nothing removes it, so the count only grows.claims.bounded.toolsrenders it as "114 finished, still labelled" and says outright it "only grows until someone drains it.".github-private#435 is the standing record of the same class. This closes its label half.What this adds
claim-sweep.yml, built as thepr-sweep.ymlsibling — weekly cron (a real run, per the #713 standing decision) plus dispatch with dry-run default. Per claimed issue, in order:stale_days(default 14 — claims are leases on attention, shorter than the 30-day PR window).Release = label removal + unassign + one comment naming the run. That is exactly what every claim record already instructs; this makes the convention a mechanism.
The standing-claim exemption is load-bearing
#273, #274, #278, #279, #280 here and
.github-private#731 are held by machine lanes sopr-claimhas an open, claimed issue to resolve. They are open, PR-less, and never refresh — precisely the shape rule 3 deletes..github-private#731 predicts this sweep by name:This sweep is #717. Without the exemption its first cron breaks every machine lane in the org — for the three fan-out lanes, in ~86 repos at once. Those six issues were all created or last touched on 08-29, so they age past the 14-day window around 2026-09-12; the exemption had to be in place before then, and now is.
Two guards, because one was not enough:
standing-claim), not on parsing amachine/*claimant out of hand-written comment prose, which is not a checkable interface.Unassign failure is reported, never fatal — the label is the half
pr-claimreads, so its removal is what ends the claim; the assignee is a human-facing projection and must not be load-bearing.Rollout
standing-claimlabel inbounded-systems/.githubandbounded-systems/.github-private— done; auto-created on first use, currently default grey (ededed) with no description..github-private#731 — done and verified: all six now carryclaimed+standing-claim, withclaimedand existing assignees preserved.[settings]Register theclaim-sweepbroker entry pinned to this workflow atrefs/heads/main, withissues. Until then cross-repo runs fail at mint naming the gap — the intended arrival state; same-repo runs work on the workflow's own token.standing-claimlabel a colour and the description "Held by a machine lane, not a session — never released by claim-sweep." No label-update API was available to this session.Verification
node --test *.test.mjs— 254 passnode --test .claude/*.test.mjs— 207 passnode --test workflows.test.mjs— 93 passThe workflow ratchet earned its keep: it rejected the first draft because the release comment had prose at column 0 inside a
run:block — a leaked block scalar. Rebuilt withjq -nc, the way_claim.ymldoes it.Not yet run against live data —
workflow_dispatchonly exposes workflows present on the default branch, so the first dry run has to follow the merge rather than precede it. Projected from the same API data the table above came from, that run should read: 92WOULD RELEASE(closed issues), 6exempt, 10keep.Noted, not fixed here
Door 1 (
claim-ticket.yml) exposes noreleaseinput — only.github-private'sclaim.ymldoes. So releasing a claim on an issue in this repo has no mechanized door today, which is part of why the label never comes off by hand either. Out of scope for this PR rather than widened into it.Parts 2 (claim feed in
front-desk-feed) and 3 (claims.bounded.toolsindesk) of.github-private#717 live in other repos. Part 3's page already exists, so that part is likely reconciliation.