fix(ticket-hygiene): a rejected label write no longer reports as a successful flag - #24
Conversation
…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>
Bugbot couldn't run - usage limit reachedBugbot 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) |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. 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 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Comment |
ApprovabilityVerdict: Needs human review Unable to check for correctness in dc50734. An unresolved review comment identifies a bug: the new You can customize Macroscope's approvability policy. Learn more. |
| 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 |
There was a problem hiding this comment.
🔴 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| # 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" |
There was a problem hiding this comment.
🔍 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
PR Summary by QodoFix ticket-hygiene sweep: treat rejected label writes as failures
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
7 rulesReview 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. 1. Caches failed label creation
|
| 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} " | ||
| ;; |
There was a problem hiding this comment.
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 FixerNo 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>
Bugbot couldn't run - usage limit reachedBugbot 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) |
|
Added a second commit ( The defectAuto-add drives off Verified against the live org rather than cited from memory: 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
Receipts
Not fixed hereThis makes the truncation loud; it does not raise the ceiling. The actual fix is to shard the query (per-repo, or by Both steps in this one workflow independently converged on the same shape: a bound enforced silently reads as "nothing to do." |
| 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 |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
The daily board sweep has been reporting success while its label writes were rejected. Found while working
finding-closureE1.P3.3 inclaude-workstation, which depends onneeds-triagemeaning something.The defect
Every label write was fire-and-forget, and the counter that feeds the summary was bumped immediately after it:
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):
The 108 are selected on every run. Their issue timelines carry no
needs-triageevent at all — neitherlabelednorunlabeled:#1146was 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 (#1297labelled 15:26:03Z,#1290at 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
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.failed > 0emits::error, lists the first 20 failures in the job summary, and exits 1.gh label create ... -R "$GITHUB_REPOSITORY"only ever created it inwave-av/.github, the one repo the sweep almost never writes to;gh issue edit --add-labelfails outright where the label is absent. Now ensured once per distinct target repo, deduped in a seen-set.classifiedandunlabeledwere initialised inside the pagination loop and re-zeroed every page, so the summary reported only the last page's counts. Hoisted out.missingconflated "this run flagged N" with "N are standing backlog being recounted".hasNextPageis still true, a::warningsays so. Today the board is 1,223 items; silent truncation at 2,000 is a live trap.Receipts
bash -nclean;shellcheck -S warningclean.Mutation-proved against a stubbed
ghonPATH, using the realedit_labelextracted from this file — the same three writes under both code shapes:Flagged **3** item(s), exit 0missing=0 failed=3, exit 1Flagged **3** item(s), exit 0missing=3 failed=0, exit 0Also 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
\x07and an ANSI escape produced a failure log containing 0 control characters.One
set -etrap was found and fixed during testing rather than shipped:[ "$_try" -lt 3 ] && sleep ...returns 1 on the final attempt, which underset -euo pipefailwould kill the step at the exact moment it was handling a failure. It is an explicitif, 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/.githubis public, so this is yours to merge — I don't self-merge here.🤖 Generated with Claude Code
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-triagelabel 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 whengh issue editactually succeeds. Any failed writes emit::error, surface sample failures in the job summary, andexit 1.The sweep summary also separates newly flagged items from standing backlog already carrying
needs-triage, hoistsclassified/unlabeledcounters 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
issueCountvs 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
gh issue editcalls increment add/remove counts.edit_label()shell function with 3 retries on rate-limit errors, stderr capture, and failure logging tolabel-failures.txt.needs-triageexists before writing.Macroscope summarized dc50734.