Skip to content

claim-sweep: the pr-sweep sibling that owns claim staleness (#286) - #287

Merged
bdelanghe merged 1 commit into
mainfrom
claude/bounded-desk-tools-3fcown
Aug 29, 2026
Merged

claim-sweep: the pr-sweep sibling that owns claim staleness (#286)#287
bdelanghe merged 1 commit into
mainfrom
claude/bounded-desk-tools-3fcown

Conversation

@bdelanghe

@bdelanghe bdelanghe commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #286

claim-ticket.yml's own header already named this file as the fix:

"Releasing a claim = remove the claimed label (and unassign). Nothing expires stale claims yet; if that starts to hurt, it is a scheduled sweep in the board-parity family, not more logic here."

The measured problem

Against the live API on 2026-08-29:

Claims on finished work, org-wide 114 (92 in this repo + .github-private)
Open claims past the 14-day window, no linked PR 3 (.github-private #373, #372, #314)
Open PRs anywhere 0 — the PR backlog is drained; the claim backlog is not

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 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 the pr-sweep.yml sibling — weekly cron (a real run, per the #713 standing decision) plus dispatch with dry-run default. Per claimed issue, in order:

  1. Exempt — a standing claim, at any age.
  2. Release iff the issue is closed, whatever closed it. A claim on a closed issue guards nothing.
  3. Release iff open, no linked open PR, untouched for stale_days (default 14 — claims are leases on attention, shorter than the 30-day PR window).
  4. Keep everything else, reason printed.

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 so pr-claim has 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:

"a standing claim never refreshes, so any wall-clock expiry (#717) would expire it first."

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:

  • Keyed on a label (standing-claim), not on parsing a machine/* claimant out of hand-written comment prose, which is not a checkable interface.
  • The staleness rule refuses to run in any repo where that label does not exist, and says why in a warning. If no standing claim there can be marked, rule 3 is not safe to apply. Rule 2 is unaffected — a standing claim is open by construction, so releasing claims on closed issues can never touch one.

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.

Rollout

Verification

  • node --test *.test.mjs254 pass
  • node --test .claude/*.test.mjs207 pass
  • node --test workflows.test.mjs93 pass

The 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 with jq -nc, the way _claim.yml does it.

Not yet run against live data — workflow_dispatch only 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: 92 WOULD RELEASE (closed issues), 6 exempt, 10 keep.

Noted, not fixed here

Door 1 (claim-ticket.yml) exposes no release input — only .github-private's claim.yml does. 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.tools in desk) of .github-private#717 live in other repos. Part 3's page already exists, so that part is likely reconciliation.

`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
bdelanghe marked this pull request as ready for review August 29, 2026 16:48
@bdelanghe
bdelanghe requested a review from a team as a code owner August 29, 2026 16:48
@bdelanghe
bdelanghe merged commit 110986b into main Aug 29, 2026
10 checks passed
@bdelanghe
bdelanghe deleted the claude/bounded-desk-tools-3fcown branch August 29, 2026 16:48
@bounded-systems-front-desk bounded-systems-front-desk Bot moved this from Todo to Done in Front Desk Aug 29, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

claim-sweep.yml — the pr-sweep sibling that owns claim staleness (.github-private#717 part 1)

2 participants