Skip to content

fix(ticket-hygiene): a rejected label write no longer reports as a successful flag - #24

Merged
yakimoto merged 2 commits into
mainfrom
fix/ticket-hygiene-silent-label-failures
Aug 4, 2026
Merged

fix(ticket-hygiene): a rejected label write no longer reports as a successful flag#24
yakimoto merged 2 commits into
mainfrom
fix/ticket-hygiene-silent-label-failures

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The daily board sweep has been reporting success while its label writes were rejected. Found while working finding-closure E1.P3.3 in claude-workstation, which depends on needs-triage meaning something.

The defect

Every label write was fire-and-forget, and the counter that feeds the summary was bumped immediately after it:

gh issue edit "$num" -R "$repo" --add-label needs-triage >/dev/null 2>&1 || true
missing=$((missing+1))

So "flagged 108 items" and "attempted 108 and every one was rejected" print the same line and both exit 0. That is the precise failure this sweep exists to detect — a state that is not what the receipt says — running inside the detector.

Evidence it is the failing case, not the working one

Replaying the sweep's own item query against org project #3 (1,223 items, 13 pages — under the 20-page cap, so truncation is not the cause):

open wave-av/claude-workstation issues on the board   288
  selected by the sweep's filter                      256
  would receive `--add-label needs-triage`            108
  actually carrying the label                         148   ← none of the 108

The 108 are selected on every run. Their issue timelines carry no needs-triage event at all — neither labeled nor unlabeled:

$ gh api repos/wave-av/claude-workstation/issues/1146/timeline --paginate \
    --jq '.[]|select(.label.name=="needs-triage")|.event'
(no output — same for #1210, #1420)

#1146 was opened 2026-07-23 and has survived four consecutive successful sweeps. Meanwhile the writes that do land were still going in during the final second of the last run's step (#1297 labelled 15:26:03Z, #1290 at 15:26:07Z, step ended 15:26:08Z).

Stated as a hypothesis, not a finding: the underlying rejection is most likely GitHub's secondary rate limit — the loop issues several hundred content-mutating requests back to back with no pacing, and the limit is roughly 80/min. I cannot prove that, because the stderr that would say so was discarded. That is itself the argument for this change: after it, the next run's summary will name the actual reason instead of leaving it to inference.

Changes

  1. Stop discarding the error. Writes go through edit_label(), which captures stderr, tests the exit status, counts failures, and records the first 160 chars in a failure log. Captured stderr is stripped of control characters before it reaches the summary — it can quote third-party repo and issue text.
  2. Fail loud. failed > 0 emits ::error, lists the first 20 failures in the job summary, and exits 1.
  3. Ensure the label in the target repo. gh label create ... -R "$GITHUB_REPOSITORY" only ever created it in wave-av/.github, the one repo the sweep almost never writes to; gh issue edit --add-label fails outright where the label is absent. Now ensured once per distinct target repo, deduped in a seen-set.
  4. Bounded retry with backoff (20s, 40s) on rate-limit rejections only — a 404 or permission error fails identically three times, so it breaks out immediately — plus a 0.4s pace between writes.
  5. Counter scoping. classified and unlabeled were initialised inside the pagination loop and re-zeroed every page, so the summary reported only the last page's counts. Hoisted out.
  6. Already-flagged counted separately from newly-flagged. The old missing conflated "this run flagged N" with "N are standing backlog being recounted".
  7. The 20-page cap is now loud. If it is hit while hasNextPage is still true, a ::warning says so. Today the board is 1,223 items; silent truncation at 2,000 is a live trap.

Receipts

bash -n clean; shellcheck -S warning clean.

Mutation-proved against a stubbed gh on PATH, using the real edit_label extracted from this file — the same three writes under both code shapes:

writes rejected old code this change
3 of 3 Flagged **3** item(s), exit 0 missing=0 failed=3, exit 1
0 of 3 Flagged **3** item(s), exit 0 missing=3 failed=0, exit 0

Also verified: the retry fires exactly 3 times on a rate-limit reply and does not sleep before giving up; the label is ensured once per distinct repo (2 ensures across 3 issues in 2 repos); and a stub emitting \x07 and an ANSI escape produced a failure log containing 0 control characters.

One set -e trap was found and fixed during testing rather than shipped: [ "$_try" -lt 3 ] && sleep ... returns 1 on the final attempt, which under set -euo pipefail would kill the step at the exact moment it was handling a failure. It is an explicit if, with a comment saying why.

Not claimed

This does not label the 108 backlogged items — it makes the next scheduled run either label them or say why it could not. If the cause is the rate limit, the pacing and retry should clear them over one or two runs; if it is something else, run #1 after merge will print it. I have deliberately not hand-labelled the backlog: that would clear the symptom and destroy the evidence for whether this fix works.

wave-av/.github is public, so this is yours to merge — I don't self-merge here.

🤖 Generated with Claude Code


Open in Devin Review

Review in cubic


Note

Medium Risk
Changes automation that mutates labels and board membership across many org repos; failures now fail the job (intentionally), and pacing may lengthen scheduled runs.

Overview
Fixes the daily ticket-hygiene board sweep so failed needs-triage label writes are no longer reported as successful flags.

Label add/remove now goes through edit_label(), which ensures the label exists in each target repo (not only the workflow repo), retries rate-limit rejections with backoff, paces writes, records stderr on failure, and only bumps success counters when gh issue edit actually succeeds. Any failed writes emit ::error, surface sample failures in the job summary, and exit 1.

The sweep summary also separates newly flagged items from standing backlog already carrying needs-triage, hoists classified / unlabeled counters so totals span all pages (they were reset per page), and warns when the 20-page board cap truncates the run.

The auto-add step now tracks issueCount vs walked results to detect GitHub search’s 1000-result truncation, upgrades the 200-add cap to a workflow warning, and reports search coverage in the step summary.

Reviewed by Cursor Bugbot for commit dc50734. Configure here.

Note

Fix rejected label writes to not count as successful flags in ticket-hygiene workflow

  • Replaces unconditional label counters with success-based counters so only confirmed gh issue edit calls increment add/remove counts.
  • Adds an edit_label() shell function with 3 retries on rate-limit errors, stderr capture, and failure logging to label-failures.txt.
  • Introduces per-repo label existence caching and creation to ensure needs-triage exists before writing.
  • Adds a 20-page traversal guard that emits a workflow warning and sets a truncated flag when hit.
  • If any label writes fail, the step now emits a workflow error annotation, surfaces the first 20 failures in the job summary, and exits non-zero.

Macroscope summarized dc50734.

…ccessful flag

The sweep discarded every label-write error (`>/dev/null 2>&1 || true`) and then
bumped its counter unconditionally, so "flagged 108 items" and "attempted 108 and
every one was rejected" produced the same green summary.

Measured: 108 open claude-workstation board items are selected for --add-label on
every run, and their issue timelines carry no needs-triage event at all — the
oldest, #1146, has survived four consecutive successful sweeps.

- capture stderr, count failures, list them in the job summary, exit 1
- ensure the label exists in the TARGET repo (it was only ever ensured in
  wave-av/.github, which the sweep almost never writes to)
- bounded retry with backoff on secondary-rate-limit rejections
- hoist classified/unlabeled out of the pagination loop (were re-zeroed per page,
  so the summary reported only the last page)
- count already-flagged items separately from newly-flagged ones
- make the 20-page cap loud instead of silently truncating

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

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

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_40df2167-eed1-4186-8238-8ccbdcc49137)

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 36 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ed957690-7a26-4cf4-a92f-3fa323ac6535

📥 Commits

Reviewing files that changed from the base of the PR and between f7d69b7 and dc50734.

📒 Files selected for processing (1)
  • .github/workflows/ticket-hygiene.yml

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

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 4, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 4, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Unable to check for correctness in dc50734. An unresolved review comment identifies a bug: the new exit 1 on label write failures will skip the subsequent "Auto-add non-WSC issues" step, breaking that functionality. This substantive issue warrants human review before merging.

You can customize Macroscope's approvability policy. Learn more.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 potential issues.

Open in Devin Review

Comment on lines +266 to +281
if [ "$failed" -gt 0 ]; then
# Loud, and non-zero. A sweep that could not write is NOT a sweep that found nothing
# to do — keeping those two apart is the whole point of this job.
echo "::error title=ticket-hygiene label writes FAILED::$failed label write(s) were rejected; the board's needs-triage state is NOT what this run reports."
{
echo ""
echo "<details><summary>First 20 label-write failures</summary>"
echo ""
echo '```'
head -n 20 label-failures.txt
echo '```'
echo ""
echo "</details>"
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Daily board sweep stops adding new issues to the board whenever a single label update is rejected

The sweep step now ends in failure (exit 1 at .github/workflows/ticket-hygiene.yml:280) as soon as any one label update is rejected, and because the step that adds missing issues to the board runs afterwards in the same job, it is skipped entirely.
Impact: One rejected label write in any of the 130+ repositories silently disables the daily auto-add of new issues to the board for that run.

Step ordering: failing step short-circuits the later "Auto-add non-WSC issues" step

edit_label records failures for any repo where gh issue edit fails (e.g. issues disabled, archived repo, app not installed, permission denied), and at .github/workflows/ticket-hygiene.yml:266-281 the step exits non-zero if failed > 0. GitHub Actions skips subsequent steps in a job once a step fails unless they declare if: always() / if: success() || failure(). The next step, "Auto-add non-WSC issues to the board" (.github/workflows/ticket-hygiene.yml:283-323), has no such condition, so it never runs on any sweep with at least one label-write rejection. Previously all label writes were swallowed with || true, so this step always ran.

Prompt for agents
The sweep step now exits 1 when any label write failed. Because the following step in the same job ("Auto-add non-WSC issues to the board") has no `if:` condition, it is skipped whenever the sweep step fails, so the auto-add functionality stops running. Consider either adding `if: always()` (or `if: success() || failure()`) to the auto-add step, moving the failure signalling to a final step that runs after auto-add, or deferring the non-zero exit to the end of the job so both responsibilities still execute.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +169 to +172
# A small pace between writes costs a few minutes on the backfill and keeps the steady
# state well under the secondary limit.
sleep 0.4
return "$_rc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Sweep runtime grows substantially with the fixed 0.4s pace plus rate-limit backoffs

Every label write now pays an unconditional sleep 0.4, and a rate-limited write can additionally sleep 20s then 40s before giving up. With the stated backlog (108+ items today, up to 2000 board items under the page cap) the steady state is a few minutes, but a broad secondary-rate-limit episode would cost up to ~60s per affected item, which for a few hundred items approaches the job/workflow time budget. Worth confirming an explicit timeout-minutes on this job so a rate-limit storm fails fast rather than burning runner minutes.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix ticket-hygiene sweep: treat rejected label writes as failures

🐞 Bug fix ✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Stop discarding label-write errors; fail the sweep when label writes are rejected.
• Ensure needs-triage exists per target repo; add retry/backoff and pacing for rate limits.
• Fix sweep accounting and reporting; warn loudly when the 20-page cap truncates results.
Diagram

graph TD
  B["Triage sweep"] --> C{{"ProjectV2 GraphQL"}} --> D["Item loop"] --> E["edit_label()"]
  E --> F{{"Issues/Labels API"}} --> E
  E --> G[("label-failures.txt")] --> H["Job summary"] --> I["Fail on errors"]

  subgraph Legend
    direction LR
    _step["Workflow step"] ~~~ _api{{"GitHub API"}} ~~~ _file[("Log file")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use GraphQL label mutations instead of `gh issue edit`
  • ➕ Potentially fewer CLI subprocesses and more structured error handling
  • ➕ Could centralize throttling by inspecting rate-limit metadata and HTTP headers
  • ➖ More bespoke implementation (GraphQL mutation plumbing, pagination, auth scopes)
  • ➖ Harder to maintain than gh-based approach for ops scripts
2. Centralized rate limiter with jitter (no fixed sleeps)
  • ➕ Better throughput under varying limits while avoiding bursts
  • ➕ Easier to tune globally (token bucket/leaky bucket)
  • ➖ More code/complexity for a workflow step
  • ➖ Still requires surfacing errors; does not replace failure logging

Recommendation: The PR’s approach is appropriate for an ops-focused workflow: keep using gh for portability, but make writes observable and correctness-preserving (count only successful writes, log sanitized stderr, and fail the job when the sweep couldn’t enforce state). The bounded retry/backoff and per-repo label ensuring address the most likely failure modes without a larger re-architecture.

Files changed (1) +99 / -11

Bug fix (1) +99 / -11
ticket-hygiene.ymlMake sweep label writes reliable, observable, and fail-fast +99/-11

Make sweep label writes reliable, observable, and fail-fast

• Introduces an 'edit_label()' helper that ensures 'needs-triage' exists in each target repo, captures stderr, retries likely rate-limit failures with backoff, and paces writes. Fixes accounting by hoisting counters outside the pagination loop, splitting newly-flagged vs already-flagged counts, making the 20-page cap explicit, and failing the job (with summarized failure details) when any label writes are rejected.

.github/workflows/ticket-hygiene.yml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 7 rules
Review mode: ⚖️ Balanced: This is a behavior-changing CI workflow in a label-management path, with retries, shell error handling, third-party inputs, summary reporting, and failure semantics that warrant a careful single-pass review.

Grey Divider


Remediation recommended

1. Caches failed label creation 🐞 Bug ☼ Reliability
Description
In edit_label(), the repo is added to ensured_repos even if gh label create failed (its errors
are suppressed), so later items in the same repo skip label creation attempts. If the label truly
doesn’t exist and the first create failed transiently, subsequent `gh issue edit ... --add-label
needs-triage` calls for that repo will continue failing for the rest of the run.
Code

.github/workflows/ticket-hygiene.yml[R143-146]

+                gh label create needs-triage -R "$_r" -c "#D93F0B" \
+                  -d "On the board but missing Type/Area/Priority" >/dev/null 2>&1 || true
+                ensured_repos="${ensured_repos}${_r} "
+                ;;
Evidence
The workflow explicitly notes that gh issue edit --add-label fails if the label does not exist in
the target repo, so ensuring the label is required per-repo. However, edit_label() suppresses `gh
label create failures with || true and still appends the repo to ensured_repos`, meaning
subsequent calls for that repo will skip label creation even if it never succeeded; the retry loop
only retries gh issue edit, not label creation.

.github/workflows/ticket-hygiene.yml[119-123]
.github/workflows/ticket-hygiene.yml[140-146]
.github/workflows/ticket-hygiene.yml[148-163]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`edit_label()` unconditionally appends the repo to `ensured_repos` even though `gh label create ... || true` may have failed. This prevents later calls in the same run from retrying label creation for that repo, which can cause repeated label-edit failures if the label still doesn’t exist.

## Issue Context
- `gh issue edit --add-label` fails if the label does not exist in the target repo.
- Current code suppresses label-create errors and still marks the repo as “ensured”.

## Fix Focus Areas
- .github/workflows/ticket-hygiene.yml[140-146]

## Implementation notes
- Capture the exit code and stderr of `gh label create`.
- Only add the repo to `ensured_repos` when:
 - label creation succeeded, OR
 - label creation failed specifically because the label already exists.
- If label creation fails for any other reason, do **not** mark the repo as ensured (so subsequent items can retry), and optionally record a failure reason (either in `label-failures.txt` or a separate log) to make root cause visible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +143 to +146
gh label create needs-triage -R "$_r" -c "#D93F0B" \
-d "On the board but missing Type/Area/Priority" >/dev/null 2>&1 || true
ensured_repos="${ensured_repos}${_r} "
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Caches failed label creation 🐞 Bug ☼ Reliability

In edit_label(), the repo is added to ensured_repos even if gh label create failed (its errors
are suppressed), so later items in the same repo skip label creation attempts. If the label truly
doesn’t exist and the first create failed transiently, subsequent `gh issue edit ... --add-label
needs-triage` calls for that repo will continue failing for the rest of the run.
Agent Prompt
## Issue description
`edit_label()` unconditionally appends the repo to `ensured_repos` even though `gh label create ... || true` may have failed. This prevents later calls in the same run from retrying label creation for that repo, which can cause repeated label-edit failures if the label still doesn’t exist.

## Issue Context
- `gh issue edit --add-label` fails if the label does not exist in the target repo.
- Current code suppresses label-create errors and still marks the repo as “ensured”.

## Fix Focus Areas
- .github/workflows/ticket-hygiene.yml[140-146]

## Implementation notes
- Capture the exit code and stderr of `gh label create`.
- Only add the repo to `ensured_repos` when:
  - label creation succeeded, OR
  - label creation failed specifically because the label already exists.
- If label creation fails for any other reason, do **not** mark the repo as ensured (so subsequent items can retry), and optionally record a failure reason (either in `label-failures.txt` or a separate log) to make root cause visible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

No findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page.

…exists

Same file, same bug class as this PR: a bound enforced silently reads as
"nothing to do".

The auto-add step drives off search(type:ISSUE), which hard-caps at 1000 results
however the caller paginates — hasNextPage just goes false, which is also the
steps normal termination condition. Verified against the live org: issueCount
reports 5216 while pagination yields exactly 1000 and stops. The true total rides
on every page, so the invariant is free.

- select issueCount; count nodes walked; ::warning if walked < declared
- ::warning if issueCount was unreadable, so a skipped check is not silence
- promote the 200-per-run cap from a bare stdout echo to a ::warning
- report search coverage in the job summary

Measured 2026-08-04: the live query returns 951 of a possible 1000 — 49 issues of
headroom. Tracked as wave-av/claude-workstation#1581.

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

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

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_92a8e59f-7d25-4a41-95f2-c06b77648848)

@yakimoto

yakimoto commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Added a second commit (dc50734) covering the sibling defect in the auto-add step of this same file — same bug class, so it is one review and one merge rather than two.

The defect

Auto-add drives off search(type: ISSUE), which hard-caps at 1000 results however the caller paginates. Past the cap hasNextPage returns false — which is also the step's normal termination condition, so a truncated run is indistinguishable from "everything is already on the board".

Verified against the live org rather than cited from memory:

issueCount for `org:wave-av is:issue`            5216
nodes actually returned by full pagination       1000   ← hasNextPage=false here

The API states the true total in the same response it truncates. That makes the assertion free — no threshold to tune:

[ "$declared" -ge 0 ] || declared="$(jq -r '.data.search.issueCount' <<<"$resp")"
walked=$(( walked + $(jq -r '.data.search.nodes | length' <<<"$resp") ))
...
elif [ "$walked" -lt "$declared" ]; then
  echo "::warning title=auto-add search TRUNCATED::walked $walked of $declared ..."

The live query returns 951 of a possible 1000 today — 49 issues of headroom, and this fleet files ~100/day.

Also in this commit

  • ::warning if issueCount could not be read, so a skipped check is not silence either.
  • The 200-adds-per-run cap was a bare echo to stdout; promoted to ::warning. The summary's Auto-added N reads as complete without it.
  • Search coverage (examined N of M) now in the job summary.

Receipts

bash -n and shellcheck -S warning clean. The assert replayed against synthetic pages using the real measured numbers:

case declared walked result
healthy 951 951 silent
capped 5216 1000 WARN: TRUNCATED (1000 of 5216)

Not fixed here

This makes the truncation loud; it does not raise the ceiling. The actual fix is to shard the query (per-repo, or by created: window) so no single search approaches 1000 — tracked as wave-av/claude-workstation#1581. Shipping the detector first is deliberate: today the cap is not being hit, and I would rather the day it starts be a red annotation than a number nobody questions.

Both steps in this one workflow independently converged on the same shape: a bound enforced silently reads as "nothing to do."

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +338 to 342
if [ "$declared" -lt 0 ]; then
echo "::warning title=auto-add could not read issueCount::the search-truncation check did not run, so this run's coverage is unverified."
elif [ "$walked" -lt "$declared" ]; then
echo "::warning title=auto-add search TRUNCATED::walked $walked of $declared matching issue(s) — GitHub caps search at 1000 results regardless of pagination. Issues beyond the cap were never examined and NOTHING will add them to the board. Shard the query (by repo, or by created: window)."
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Board auto-add reports a false 'search truncated' alarm whenever it stops early at its own limits

The coverage check compares how many issues were examined against the total reported by the search ([ "$walked" -lt "$declared" ] at .github/workflows/ticket-hygiene.yml:340) even when the run deliberately stopped early at its own 200-add or 20-page limit, so it raises a misleading truncation alarm about data loss that did not happen.
Impact: Operators see a scary warning claiming issues were never examined and nothing will add them, on perfectly normal capped runs.

Early breaks make walked < declared by design

The loop breaks out early on [ "$added" -lt 200 ] || { capped="yes"; break; } (.github/workflows/ticket-hygiene.yml:316 and :326) and on the page cap [ "$page" -le 20 ] || break (.github/workflows/ticket-hygiene.yml:293). In both cases walked is necessarily below declared, yet the assert at .github/workflows/ticket-hygiene.yml:340-341 unconditionally attributes the shortfall to GitHub's 1000-result search cap and tells the reader to shard the query. The check should only fire when the loop terminated normally (i.e. capped != "yes" and the page cap was not hit).

Prompt for agents
In the 'Auto-add non-WSC issues to the board' step, the walked-vs-declared truncation assert fires even when the loop exited early because of the 200-add cap or the 20-page cap, producing a false 'search TRUNCATED / GitHub caps search at 1000 results' warning. Track whether the loop ended normally (no capped="yes", no page-cap break) and only run the walked < declared comparison in that case; otherwise report the coverage figure without the truncation claim.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@yakimoto
yakimoto merged commit 7767662 into main Aug 4, 2026
11 checks passed
@yakimoto
yakimoto deleted the fix/ticket-hygiene-silent-label-failures branch August 4, 2026 21:55
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.

1 participant