Skip to content

fix(project-board): refuse truncated board reads instead of trusting them - #2699

Merged
devantler merged 5 commits into
mainfrom
claude/board-health-check-truncation-2698
Aug 7, 2026
Merged

fix(project-board): refuse truncated board reads instead of trusting them#2699
devantler merged 5 commits into
mainfrom
claude/board-health-check-truncation-2698

Conversation

@devantler

@devantler devantler commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Caution

PARKED — CI is wedged GitHub-side on this branch. Do not promote; do not merge.

Measured 2026-08-06T23:2xZ. This branch's CI run (31125693200, created 18:18:41Z during the
Actions major_outage) is a zombie: gh run view reports status: queued while
gh run cancel refuses with "Cannot cancel a workflow run that is completed". The two APIs
disagree and neither state can be acted on.

Consequently no CI has run at the current head b73a1420e7 — the only check-runs present are
two CANCELLED CodeQL analyses from the previous head ef543c2a41. close + reopen (which fires
pull_request: reopened, a default type for this workflow) produced no new run.

Positive control, so this is not a repo-wide CI failure: monorepo CI ran green on a different
branch (claude/cred-table-blob-run-evidence-2522) at 22:56:59Z. The wedge is specific to this
branch's concurrency group ci-CI-refs/pull/2699/merge.

Unblocks when GitHub reaps the zombie run, or the next real push to this branch dispatches a
fresh event. Nothing in the diff is implicated — no code fix is owed here.

🤖 Generated by the Agentic Engineer

Why

The board's health checks are how we notice the board drifting. Right now they can't be trusted: the card tells a run to read the board with "an explicit high limit", and that quietly returns a partial board with no sign anything was missing — so a check comes back "100% clean" off ~60% of the data. A prior run nearly filed a fabricated finding off exactly this.

It also has a side effect worth caring about: one board read consumes nearly the whole hourly GitHub query budget, which all three agent lanes share. When that budget is gone, the PR review-thread checks come back empty too — and empty reads as "nothing unresolved", which is the wrong way for a safety check to fail.

What

Stops treating a partial board read as an answer. Coverage is now checked from the issue's side, which is cheap and exact; where the whole board really is needed, the read has to prove it got all of it or refuse. Both failure modes now stop the check instead of silently passing it.

Fixes #2698

Blocked — GitHub Actions outage (not a defect in this change)

Parked pending GitHub recovery. GitHub reported Actions: major_outage (verified live on githubstatus.com at 2026-08-06T22:03Z). At this head no CI run dispatched at all; at the previous head the changes job and CI - Required Checks were cancelled by the platform with every downstream job skipped — an infrastructure cancellation, not a test failure. Re-running is wasted spend until Actions recovers. The CodeRabbit / success — Review rate limited status is provider quota, not a verdict.

devantler and others added 2 commits August 6, 2026 20:09
…them

The card told a run to "pass an explicit high limit" to gh project
item-list. Measured 2026-08-06, that is not sufficient: --limit 3000
returned exactly 3000 items against a true totalCount of 4988, with no
warning, no error and exit 0 -- so ~40% of the board was silently absent
and every health check below would have reported clean.

The same pass also left the GraphQL budget at 73/5000. That budget is
attached to the user, so all three lanes share it, and the surveyor's
paginated reviewThreads queries are what the pentad's unresolved-thread
count depends on -- a starved pentad reads clean, failing open on the
promotion gate.

Prefer per-issue projectItems membership (~1 point, and it distinguishes
"on no project" from "on project 5"). Where the whole board is genuinely
needed, pair the enumeration with a truncation guard that treats
returned == limit or returned < totalCount as a hard failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…easured reason

Self-review flagged the cond && { ...; exit 1; } form. The first
justification written for it was wrong and testing caught it: set -e does
NOT abort mid-script there, because it exempts a non-final component of
an AND-OR list.

The real hazard, measured: on the healthy path that form evaluates to 1,
so as the last command of a script or function it returns a spurious
failure -- and as a function's last line it took a set -e caller down
(outer rc=1). The if-form has no such edge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Self-review done at this head before requesting: 54-line docs-only change to one skill file, the
measured claims match the run evidence they cite, and the guard snippet fails closed in both arms
(exactly-at-limit and below-total). No P0/P1 from my own pass.

Requesting review now rather than after CI because every check on this head is CANCELLED/SKIPPED
by the active GitHub Actions outage, not by a defect — CodeRabbit does not depend on Actions, so the
review can proceed in parallel and the head will not move while it runs.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

@devantler I will review pull request #2699.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updated project-board health-check guidance to avoid broad item enumeration for coverage checks. Per-issue projectItems queries now verify membership. Whole-board enumeration requires comparing returned items with totalCount, rejecting truncated results, avoiding false failures under set -e, and preserving API error output. The guidance also documents shared GraphQL budget exhaustion.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: refusing truncated board reads instead of treating them as complete.
Description check ✅ Passed The description directly explains the board-read risks, the implemented safeguards, and the CI outage context.
Linked Issues check ✅ Passed The documentation changes satisfy issue #2698 by enforcing truncation checks, per-issue coverage checks, budget warnings, and fail-closed behavior.
Out of Scope Changes check ✅ Passed The changes stay within the linked issue scope and update only project-board health-check guidance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/products/project-board/SKILL.md:
- Around line 50-54: Update the item-list retrieval around gh project item-list
so its command status is checked with if !, preserving the command’s output and
stderr while rejecting failures. Validate both n and total after extraction as
non-empty integer counts, including null and non-numeric values, before
performing numeric comparisons; only run the existing truncation and
completeness checks after validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e74dd6f6-1795-467d-8833-944e7c80d2c9

📥 Commits

Reviewing files that changed from the base of the PR and between d0a7a9e and ef543c2.

📒 Files selected for processing (1)
  • .claude/skills/products/project-board/SKILL.md
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-07-07T16:28:59.865Z
Learnt from: devantler
Repo: devantler-tech/monorepo PR: 2077
File: .claude/adr/0001-kagent-agent-fleet-substrate.md:35-35
Timestamp: 2026-07-07T16:28:59.865Z
Learning: In this repo, markdownlint CI only runs for `docs/**`, so Markdown files under `.claude/` (e.g., ADRs like `.claude/adr/*.md`) won’t be automatically linted. During code review, treat `.claude/**/*.md` as needing the same markdownlint-clean style as the docs—e.g., avoid MD022 issues like missing/incorrect blank lines around headings—and flag markdownlint violations even though CI won’t catch them.

Applied to files:

  • .claude/skills/products/project-board/SKILL.md
🪛 SkillSpector (2.5.1)
.claude/skills/products/project-board/SKILL.md

[error] 29: [AR2] Anti-Refusal Statement: Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Remediation: Remove instructions that suppress warnings, disclaimers, or ethical commentary. Let the agent surface safety-relevant caveats to the user.

(Anti-Refusal (AR2))


[warning] 29: [OH3] Unbounded Output: Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Remediation: Set explicit limits on output length, generation count, and rate. Use max_tokens and truncation to prevent unbounded output.

(Output Handling (OH3))


[error] 3: [YR4] YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]: YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Remediation: Remove offensive tool references and exploit code. Legitimate agent skills should not contain penetration testing tools, exploit frameworks, or reconnaissance utilities.

(YARA Match (YR4))

Comment thread .claude/skills/products/project-board/SKILL.md
…rors

The guard read its count through a single pipeline, so a non-zero gh left the
count empty. Both integer comparisons then errored, and a failing [ inside an
if condition is exempt from set -e, so the guard reached its success path and
exited 0 having counted nothing. Each call is now checked on its own line and
both counts are asserted numeric.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Re-requesting at the new head. b73a142 fixes the 🟠 Major fail-open you found — the guard now
checks each gh call separately and asserts both counts numeric, verified refusing on a gh
failure (previously exit 0), still tripping on a genuine truncation, and still passing a healthy full
read. Thread fixed and resolved; unresolved count is 0 at this head.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

@devantler I will review pull request #2699 at the requested head.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler devantler closed this Aug 6, 2026
@devantler devantler reopened this Aug 6, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

The 20:22Z request was rate limited and returned an empty reply container rather than a review, so the current head still carries no external review. Re-requesting now that the window has passed.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

@devantler I will perform a full review of pull request #2699 at the requested head.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/products/project-board/SKILL.md:
- Around line 42-44: Update the project-item lookup described in the project
board instructions to resolve the expected organization project by owner and
compare its globally unique project.id, rather than matching project.number
alone. Ensure the query or comparison includes owner identity so projects with
the same number under different owners cannot match.
- Around line 42-44: Update the project membership query in the project-board
instructions to paginate projectItems using pageInfo.hasNextPage and
pageInfo.endCursor, continuing until all pages are checked before declaring an
issue missing; alternatively enforce a documented repository-specific maximum
that safely covers all memberships.
- Around line 42-44: Update the Issue.projectItems query in the project-board
skill to pass includeArchived: false, ensuring coverage reads exclude archived
project items while preserving the existing project number lookup.
- Around line 41-44: Update the per-issue projectItems verification around the
gh api graphql read to fail closed: preserve stderr, check the command exit
status, and report the membership result as unverified when the read fails. Only
interpret an empty projectItems result as “not on a project” after a successful
query.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5ee1ffc3-5997-4cb4-9a51-4e8a8a0728c6

📥 Commits

Reviewing files that changed from the base of the PR and between d0a7a9e and b73a142.

📒 Files selected for processing (1)
  • .claude/skills/products/project-board/SKILL.md
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-07-07T16:28:59.865Z
Learnt from: devantler
Repo: devantler-tech/monorepo PR: 2077
File: .claude/adr/0001-kagent-agent-fleet-substrate.md:35-35
Timestamp: 2026-07-07T16:28:59.865Z
Learning: In this repo, markdownlint CI only runs for `docs/**`, so Markdown files under `.claude/` (e.g., ADRs like `.claude/adr/*.md`) won’t be automatically linted. During code review, treat `.claude/**/*.md` as needing the same markdownlint-clean style as the docs—e.g., avoid MD022 issues like missing/incorrect blank lines around headings—and flag markdownlint violations even though CI won’t catch them.

Applied to files:

  • .claude/skills/products/project-board/SKILL.md
🪛 SkillSpector (2.5.1)
.claude/skills/products/project-board/SKILL.md

[error] 29: [AR2] Anti-Refusal Statement: Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Remediation: Remove instructions that suppress warnings, disclaimers, or ethical commentary. Let the agent surface safety-relevant caveats to the user.

(Anti-Refusal (AR2))


[warning] 29: [OH3] Unbounded Output: Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Remediation: Set explicit limits on output length, generation count, and rate. Use max_tokens and truncation to prevent unbounded output.

(Output Handling (OH3))


[error] 3: [YR4] YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]: YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Remediation: Remove offensive tool references and exploit code. Legitimate agent skills should not contain penetration testing tools, exploit frameworks, or reconnaissance utilities.

(YARA Match (YR4))

🔇 Additional comments (1)
.claude/skills/products/project-board/SKILL.md (1)

26-40: LGTM!

Also applies to: 46-80

Comment thread .claude/skills/products/project-board/SKILL.md Outdated
Comment thread .claude/skills/products/project-board/SKILL.md Outdated
…-closed bar

The per-issue query replaced enumeration but inherited its traps. It
matched the board by project number, which is unique only within one
owner, so a repository-level or other-owner project of the same number
satisfied it and reported an unboarded issue as covered. It took the
first 20 items with no pagination, which truncates exactly as the
enumeration does and answers not on the board for an issue that is. And
it could not tell an empty result from a failed query, so a read error
read as verified absence.

The read now resolves the board by id, paginates to exhaustion, excludes
archived items explicitly, and returns three distinct outcomes: on the
board, verified absent, and unverified. Verified against live data: a
boarded issue returns 0, platform#1 returns 1, and an unresolvable
repository returns 2.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit reviewed b73a142 and its four findings are fixed in 5e669e9, which stales that review. CodeRabbit is rate limited for the next 51 minutes, so advancing to the next lane for the new head.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit is rate limited (51 min) and Codex reports its code-review usage limit is reached, both at this head. Advancing to the third lane.

@devantler

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9098fbef-88af-4764-97f5-f31624632071)

…bles

The helper assigns in the caller's scope, so its page and cursor
variables are now prefixed rather than taking the common names page and
after from whatever copies it. Its structural probe no longer discards
stderr, matching the rule this same card states two paragraphs later: a
suppressed error is indistinguishable from an empty result. The two
behaviours a reader is most likely to mistake for bugs — the typed null
on the first page and index returning a truthy zero — are recorded next
to the code so neither gets corrected into a fail-open.

@devantler devantler left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Generated by the Agentic Engineer

Self-review (fallback — CodeRabbit, Codex and Cursor Bugbot unavailable)

Reviewed commit: 4440d8684aed3c6fe7ae75470f9b9b0b63a37261

All three lanes were requested in priority order at this PR's heads today and each returned a provider quota state, not a verdict. None states a usable retry window inside this run:

  • CodeRabbit — delivered a real review at b73a142 (4 actionable findings, all fixed in 5e669e9), then rate limited: "Next review available in: 51 minutes" (01:15Z).
  • Codex"You have reached your Codex usage limits for code reviews" (01:26Z).
  • Cursor Bugbot — check-run neutral with output.title: Error, comment "Bugbot couldn't run - usage limit reached" (01:29Z). The Error title means the run never started, as distinct from a review that found nothing.

What I checked, behaviourally

This PR is a procedure card, so the thing that can be wrong is the recipe. I extracted the on_board helper verbatim from the rendered card and ran it against live data, asserting all three outcomes rather than only the happy one:

case expected result
an issue known to be on the board 0 0
platform#1 (the card's own negative control) 1 1
an unresolvable repository 2 2

The third is the one that matters: before this change a failed read and an empty result were the same answer downstream, which is the fail-open the PR exists to close. Re-ran after the variable rename, so the verification is against the shipped text and not an earlier draft.

Findings I raised on my own diff, and fixed

  • The helper assigned page and after in the caller's scope — common enough names that a copy of this snippet could clobber them. Prefixed.
  • Its structural probe discarded stderr, two paragraphs above the card's own rule that a suppressed error is indistinguishable from an empty result. Removed.
  • The explanatory note landed inside the fence rather than after it. Fence balance re-checked (6, even).

Reviewed for the failure this card is about

Every claim in the changed text is one I can point at evidence for: the --limit 3000 → 3000-of-4988 measurement, the shared-budget figure, and the set -e behaviour of the && form (which the card correctly warns against and does not use). The two lines most likely to be "corrected" into a fail-open — the typed null cursor and index() returning a truthy 0 — now carry their reasoning inline.

Verdict: no P0/P1 findings

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness at 4440d868 — promoting

  • Programmatically tested — 30 checks, 0 failing, 0 pending; CI - Required Checks (the only required context in this repo's rulesets) success. mergeStateStatus: CLEAN, 0 unresolved threads, 0 outstanding review findings: CodeRabbit's four at b73a142 are each fixed and answered, and that review is historical against this head.
  • Reviewed — clean local review round at this exact SHA, with per-lane evidence recorded (CodeRabbit rate limited 51 min, Codex usage limit, Bugbot Error/usage limit). Fallback conditions met, and the round found and fixed three things before it was posted.
  • Tried and evaluated as a user — the on_board helper was extracted verbatim from the rendered card and run against live GitHub, asserting all three outcomes (boarded → 0, platform#1 → 1, unresolvable repo → 2), and re-run after the final edit so the check is against the shipped text.

@devantler
devantler marked this pull request as ready for review August 7, 2026 01:33
@devantler
devantler merged commit 59fa2ff into main Aug 7, 2026
34 checks passed
@devantler
devantler deleted the claude/board-health-check-truncation-2698 branch August 7, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(project-board): board health checks silently truncate and exhaust the shared GraphQL budget

1 participant