skills/maintainer/stale-sweep/SKILL.md
This repo runs no stale bot (.github/ has no stale workflow), so closing is always a deliberate
maintainer act. The value of a sweep is therefore not the list of old things, which anyone can get
by sorting on a date. It's the judgment about which silences belong to the contributor, which belong
to us, and which are the backlog working as intended.
Given a window (default 60 days), deliver a sweep the maintainer can work through in one sitting: every candidate sorted into close / nudge-author / on-us / leave-alone, each with the date and reason that put it there, plus a paste-ready comment wherever one is warranted.
gh authenticated for googleapis/mcp-toolbox. A GitHub MCP server substitutes: the commands
below map to its read/list tools.Read these live, not from memory.
60-day silence rule on
status: waiting for response/status: feedback wanted, the SLO response and closure targets, and the canonical comment templates.
gh label list --repo googleapis/mcp-toolbox --limit 200: there is no stale label, so never
propose one. This workflow's vocabulary is status: waiting for response, status: feedback wanted, duplicate, and wontfix..github/blunderbuss.yml:
which product: label routes to which team, needed to name an owner on anything blocked on us.CUTOFF=$(date -d '60 days ago' +%F 2>/dev/null || date -v-60d +%F) # GNU, then BSD/macOS
gh search issues --repo googleapis/mcp-toolbox --state open --updated "<$CUTOFF" \
--limit 100 --json number,title,url,updatedAt,createdAt,labels,author,commentsCount
gh search prs --repo googleapis/mcp-toolbox --state open --updated "<$CUTOFF" \
--limit 100 --json number,title,url,updatedAt,labels,author,isDraft
gh search defaults to 30 results, so always pass --limit.
updatedAt decideupdatedAt is not a silence measure. Label edits, bot comments, renovate rebases, and
cross-references all bump it, so an item a bot has kept warm never enters the candidate list at all
despite nobody having looked at it in months. Get the real last-human-touch before judging anything:
gh issue view <n> --repo googleapis/mcp-toolbox --json comments,labels,assignees,createdAt \
--jq '.comments[-3:] | map({author: .author.login, at: .createdAt, body: .body[0:200]})'
Discount label-only churn and bot comments (gemini-code-assist, renovate, release automation,
the Cloud Build failure reporter). What's left is the last substantive human comment, and that date
drives every call below.
When the sweep is scoped to one area rather than the whole repo, widen the search window and filter on last human comment yourself, so bot-warmed items can't hide behind the cutoff.
The highest-value step, and the one a stale bot structurally cannot do. Before drafting any nudge or close, check whether the thing fixed itself while nobody was looking:
git log --oneline --since=<created date> --grep=<term>, plus a grep for the feature or symbol.Either outcome leaves the stale buckets entirely, and both beat silence followed by a timeout:
<sha>, shipped in vX.Y.Z, citing the commit. That's a real answer,
not a stale close.duplicate plus a link to the newer issue.Stale is not the same as closeable. Put every remaining candidate in exactly one bucket.
Blocked on the author. Carries status: waiting for response or status: feedback wanted, or
the last comment is a maintainer question that never got a reply.
Blocked on us. A report filed with enough information and no maintainer reply, or a contributor PR sitting on review or on maintainer-triggered CI.
product: label
per blunderbuss.yml, and give the SLO clock from the playbook table.Blocked on nobody. Triaged feature requests in the backlog, status: help wanted,
good first issue, p2/p3 nice-to-haves.
When the bucket is a real toss-up, ask. A wrong nudge is cheap; a wrong close is not.
PRs go stale for reasons issues don't:
renovate, release-please): never nudge a bot, and draft no comment. Checks
green means merge; superseded by a newer bot PR means close.isDraft): silence is expected. Leave alone unless it's months old, and then only a
light "still planning to pick this up?".tests: run label or a /gcbrun comment. This is an on-us item wearing
contributor-silence clothing, so check it before reading quiet as abandoned.Adapt the playbook's templates rather than inventing wording. Two shapes:
Nudge, naming the specific unanswered thing and a date:
Hi @<author>, following up here. We're still blocked on <the specific thing: the repro steps, the Toolbox version, a reply to the question in <link>>. If we don't hear back in the next 14 days we'll close this out, though you're welcome to comment or reopen any time after that.
Close, giving the reason, reopenability, and thanks:
Closing this out: we weren't able to move forward without <the missing thing>, and it's been quiet for <N> days. That's not a judgment on the report, so please comment or reopen if you're still hitting this and we'll pick it back up. Thanks for taking the time to file it!
Keep two things out of both. A form-letter tone, since these are hand-sent precisely so they don't read like a bot. And any phrasing that puts the delay on the contributor when part of it was ours.
gh issue close/comment/edit, gh pr close/comment/merge, or apply a
label. Deliver the sweep in chat.tests: run.gh label list, and no stale label exists.updatedAt, so the maintainer can
check your arithmetic.file:line; a routing claim cites blunderbuss.yml. Mark anything you
couldn't verify [UNVERIFIED] rather than asserting it.## Stale sweep: <N>-day window (cutoff <YYYY-MM-DD>), <X> candidates
**Already resolved, close with an answer** (<n>)
| # | title | resolved by | last human |
**Close (author silent >60d)** (<n>)
| # | title | last human | what we asked for |
**Nudge author** (<n>)
| # | title | last human | the specific ask |
**On us (SLO miss, do not close)** (<n>)
| # | title | waiting since | owning team | SLO status |
**PRs (merge or close)** (<n>)
| # | title | why |
**Leave alone** (<n>)
- #<n>: <why the silence is fine>
**Draft comments**
### #<n>: <close | nudge | resolved>
<paste-ready body>
**Apply:**
gh issue comment <n> --repo googleapis/mcp-toolbox --body "..."
gh issue close <n> --repo googleapis/mcp-toolbox --comment "..."