docs(skills): stop board lookups from trusting item-list --limit - #2452
Conversation
gh project item-list truncates silently past --limit, and board #28 now holds more than the 500 the lookups asked for, so existing cards read as missing. Issue cards are now looked up from the issue's projectItems, which is independent of board size. Draft-card lookups and whole-board dumps (GHSA lookup, snapshot, recovery, triage sweep and audit) use --limit 2000 and assert .items|length == .totalCount, failing closed on a truncated listing or a failed gh call. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Moderate issues could permit stale IDs, invalid mutations, successful failure paths, or truncated lookups.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 3
Open (3)
What changed in this PR
Hardens board and triage recipes against silently truncated project listings and unreliable card lookups.
Changes:
- Adds completeness checks for board dumps.
- Uses issue-side GraphQL card lookup.
- Improves GHSA, snapshot, recovery, and triage handling.
Review findings:
- Moderate (2 votes): Clear stale
ITEM_IDvalues when lookup fails. - Moderate (4 votes): Prevent mutations when
ITEM_IDis empty. - Moderate (2 votes): Snapshot failure guards at lines 283 and 308 must exit non-zero.
- Moderate (1 vote): Paginate
projectItems(first:20)to avoid silent truncation.
| File | Description |
|---|---|
.claude/skills/issue-triage/SKILL.md |
Validates complete board listings during triage and audits. |
.claude/skills/board-ops/SKILL.md |
Adds safer card lookups and dump validation, with unresolved fail-closed and pagination issues. |
…#2452 review) Clear ITEM_ID before the GHSA lookup so a failed listing cannot leave an earlier id in place; guard item-edit and item-delete on a non-empty id, and split the issue-side lookup from the Status edit so the delete recipe does not inherit it; end every dump guard with false so an incomplete dump returns non-zero; widen projectItems to first:100. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot review, round 1: 3 comments, all fixed in eabbfc4. Each thread has its own reply.
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The project #11 recipe can mutate or delete against the wrong project configuration.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (3)
…2452 review) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot review, round 2: 1 comment, fixed in 4964bb6. It has a reply in its thread.
The 3 round-1 findings show as resolved. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The recovery recipe’s critical failure path does not reliably stop subsequent steps.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (1)
A deleted dump still let the redirect create an empty lost-ids.json, so the re-apply loop ran zero times and exited 0. lost-ids.json is now written only inside the completeness check, and step 3 refuses to run without it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot review, round 3: 1 comment, fixed in 68dc5f5. It has a reply in its thread.
|
…iew) The snapshot is the only record of what the orphaned cards held, and an incomplete one is now deleted, so step 1 drops lost-ids.json when the snapshot report cannot run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot review, round 4: no inline comments, but the headline raised a concern ("can retain
|
|
Copilot review, round 5: 1 comment, declined; the reasoning is in its thread.
No actionable findings remain, so the review cycle ends here. |


Closes #2451
Problem
gh project item-list --limit Ntruncates silently. Board #28 now holds 534 items, and theboard-opslookups asked for 500, so a card past that point read as missing. This produced two confidently wrong conclusions in one session: a Done card and all 16 GHSA drafts were reported absent.What changed
Issue cards are looked up from the issue (
board-ops→ Move an existing card, Delete a card). The lookup queries the issue'sprojectItemsthrough GraphQL and selects the card by the board's node id. The result no longer depends on board size. It also rules out another repo's issue sharing the number, and a user-owned project that happens to be numbered 28. The|| ITEM_ID=guard matters becausegh apiprints the raw error JSON to stdout on a GraphQL error (a PR number, a rate limit). Without the guard, that error JSON would become a non-empty "id".Full listings stay full, and prove it. The GHSA draft lookup, the pre-edit snapshot, the recovery dump, and
issue-triage's sweep and audit all need the whole board. They now use--limit 2000and assert.items | length == .totalCount(item-list --format jsonalready returnstotalCount). They fail closed:The same check catches a failed
ghcall, whose empty output has neither key.A new short section, Finding a card without trusting
--limit, states the rule once. The stale "~265 items" figure is gone, since the assertion replaces the manual check.Verification
Each recipe was extracted from the edited Markdown and run against the live boards:
--limit 100ITEM_IDplus a message; before the||guard, #99999 put the error JSON intoITEM_IDBefore this change, that same rate-limit failure made the sweep list every open issue as unboarded.
npm run local:gatepasses.Not changed:
gh issue list --state all --limit 2000in the audit. That is the same class of problem, but the repo has 1,170 issues, and the lookup inissue-createis--search-filtered.🤖 Generated with Claude Code