Repository navigation
Description
Problem
During the 2026-10-22 GitHub incident, the search API answered total_count: 0 with incomplete_results: true (repo:melodic-software/claude-code-plugins is:issue is:open returned 0; the real count was about 601). #9089 makes the one REST caller that gates admission (check-wip-cap) treat that as unknown. These callers read search through GraphQL or gh ... --search, which expose no incomplete signal, so a false empty reads as a real empty:
plugins/code-tidying/.../open-pr-count.sh:42: open-PR cap (a false 0 admits more)plugins/playbooks/.../repo-sweep/scripts/state.sh:63: dedupe before filing (a false empty files a duplicate)plugins/harness-ops/.../morning-brief.sh:1111: report only; readsissueCount, so a floor check is possibleplugins/harness-ops/.../restart-consumer.sh:866: report onlyplugins/source-control/skills/babysit-prs/scripts/babysit_gh.py:365,457: discovery; whether a PR missing from discovery releases a lease is unverifiedplugins/work-items/skills/work-item-tracker/adapters/github/list-items.sh:48
Fix
For each caller that acts on a count or an empty set (the first two, and babysit discovery if a miss releases a lease): read through REST /search/issues and fail closed on incomplete_results: true, or cross-check a GraphQL issueCount against a non-search count before acting on an empty. Report-only callers say "count unavailable" instead of 0.
Activity
github-actions commentedon Oct 22, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Understood: scripts that read GitHub search through
ghor GraphQL cannot seeincomplete_results, so a false empty reads as a real empty. A recorded decision sets the fix per script. It covers open-pr-count, state, morning-brief and restart-consumer, each with its tests. Babysit discovery and list-items are out of scope.Decision: #9090 (comment)
The decision settles every open question, so nothing further is asked.
Automated triage, applied by the lanes App.
github-actions commentedon Oct 26, 2026
The triage sweep dispatched intake triage for this issue (attempt 1 of 2).
melodic-automation-lanes commentedon Oct 26, 2026
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed
- [ ]criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).melodic-automation-lanes commentedon Oct 26, 2026
The latest triage verdict holds nothing: the issue is at
status: ready.melodic-automation-lanes commentedon Oct 26, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: The open-PR cap and the repo-sweep dedupe fail closed on an incomplete search, and report-only callers print count unavailable instead of 0.
Related work
Open questions
RECOMMENDED: Out of scope: babysit_gh.py has no lease code, and discovery only builds a queue that is rebuilt on the next run. Basis: plugins/source-control/skills/babysit-prs/scripts/babysit_gh.py:1473
RECOMMENDED: Use REST /search/issues via gh api and fail closed on incomplete_results true or any error, as #9089 did. Basis: 9089
RECOMMENDED: Fail closed in open-pr-count.sh and repo-sweep state.sh; make morning-brief, restart-consumer and github list-items.sh report unavailable after re-locating them. Basis: plugins/code-tidying/skills/tidy/scripts/open-pr-count.sh:42
RECOMMENDED: List open PRs without --search and filter the head ref locally with jq, which avoids search entirely. Basis: plugins/playbooks/skills/repo-sweep/scripts/state.sh:63
Sources read
Automated refinement by the lanes App. Answer with a comment whose first line is
**Decision recorded.**.kyle-sexton commentedon Oct 26, 2026
Decision recorded. The owner answered in the Refinement and Issue Triage sessions (2026-10-25), deciding the open items below on the facts:
No owner ledger (
.work/*/interview-checklist.md,.work/gh-monitor-owner-decisions/) names this issue.Questions from intake's refinement (#9090 (comment)):
Decided: No. A PR missing from discovery releases no lease. Leases live in
babysit_lease.py, notbabysit_gh.py. Reaping is keyed onexpires_atalone (plugins/source-control/skills/babysit-prs/scripts/babysit_lease.py:275-283). The worktree prune drops a lease only after a per-PRgh pr viewreads the PR as CLOSED or MERGED (prune_babysit_worktrees.py:408-423,:686-688), not from the discovery list. Discovery is also not search-only: repo mode lists each repository withgh pr listand no--search(babysit_gh.py:1356-1378,:1460-1464), and owner mode unions the search with that listing (babysit_gh.py:1466-1471).Decided: Do not use search. Both gating callers already filter the head ref locally after the read (
open-pr-count.sh:42counts with--jq;plugins/playbooks/skills/repo-sweep/scripts/state.sh:64-65re-checksheadRefNameagainst^chore/repo-sweep-). So the smaller fix is to drop--searchand filter the prefix locally, which avoids the search API, as the refinement's question 4 recommends. A listing without a prefilter returns every open PR, so it must set--limitand treat a full page as incomplete. Otherwise gh's default page of 30 (plugins/work-items/tools/work-item-tracker/adapters/github/list-items.sh:4) would read low and admit more, which widens the cap. The precedent is babysit's limit check (babysit_gh.py:1388-1391).Decided: For
open-pr-count.sh, the tidy skill carries on when the count is unknown (plugins/code-tidying/skills/tidy/SKILL.md:21) and stops only when 3 or more are open (SKILL.md:111). So printingThrottle active: unknownis fail-open. A listing that reaches its limit printsThrottle active: yes, which stops tidy without editing the skill. Forstate.sh, exit 10 means no sweep PR (state.sh:29,:68), and a false 10 files a duplicate. A listing that reaches its limit exits 1, the error code (state.sh:28).Decided: Each one is checked against a non-search source the script already has:
morning-brief.sh:1119-1146) and prints(none ...)on an empty (:1268-1272). Its Queues counts go throughgh issue list --label(:1020-1025), which gh serves from search (list-items.sh:25-28). The script already has a non-search counter,count_label_rest(morning-brief.sh:1027-1033, RESTrepos/.../issues?labels=). A 0 from search is cross-checked against it, and the label showscount unavailablewhen REST reads more than 0.resolve_issue_by_title(restart-consumer.sh:876-882) readsgh issue list --search. An empty becomes ano-telemetryrecord (:1082-1085). A failed read already returns 1 and becomesapi-errorwith no restart (:870-875,:1077-1080). The lookup moves to RESTsearch/issuesand returns 1 whenincomplete_resultsis notfalse, ascheck-wip-cap.mjs:100-101does. The telemetry search at:1247-1249is pinned byte-identical to morning-brief's (:1240-1246), so it is left unchanged.list-items.sh?Decided: No change. It prints no count. Its envelope's
truncatedflag is the completeness signal (plugins/work-items/tools/work-item-tracker/CONTRACT.md:268-276). Only a--labelread uses search (list-items.sh:25-32), and no script inplugins/passes--labeltolist-itemsorlist-frontier(git grep -nE '(list-items|list-frontier)\b[^|]*--label' origin/main -- plugins, excluding tests, conformance and adapters, finds only docs). A false empty then means no candidates, which picks no work. Adding an envelope field would change five adapters for no consumer.Acceptance criteria:
plugins/code-tidying/skills/tidy/scripts/open-pr-count.shlists open PRs without--search, with an explicit--limit, and counts head refs startingchore/tidy-locally. A listing that reaches the limit printsThrottle active: yes.plugins/playbooks/skills/repo-sweep/scripts/state.shlists open PRs without--searchand keeps its localheadRefNamefilter. A listing that reaches its--limitexits 1, never 10.morning-brief.shprintscount unavailablefor a label whose search read gives 0 whilecount_label_restgives more than 0, in both the Queues and stuck sections.restart-consumer.shresolve_issue_by_titlereads RESTsearch/issuesand returns 1 whenincomplete_resultsis notfalse, so the lane recordsapi-error, notno-telemetry.open-pr-count.test.sh,state.test.sh,morning-brief.test.sh,restart-consumer.test.sh) covers its new case: a full page, a search 0 against a REST count above 0, andincomplete_results: true.melodic-automation-lanes commentedon Oct 27, 2026
This records a re-fire of an earlier refusal, not a new refusal. The lanes kill switch stopped run 40441477886, the work-resolve-issue run that
agent-readystarted, before it did any work; that run ended at 2026-10-27T04:32:48Z. work-unblock has now removed and addedagent-readyback to start a fresh run. A kill-switch refusal is no fault in this issue or the lanes, so it never counts against later re-fires; after limits.limit-stops-per-issue of them since a person last addedagent-readyor a run last reported, work-unblock waits longer before each one, up to 24 hours.