Skip to content

fix(dev-lead): a bot-comment retry cancelled before it starts no longer holds the pending window or an attempt (#2262) - #2267

Merged
don-petry merged 2 commits into
mainfrom
fix/issue-2262-cancelled-bot-comment-retry
Oct 11, 2026
Merged

don-petry merged 2 commits into
mainfrom
fix/issue-2262-cancelled-bot-comment-retry

Conversation

@don-petry

@don-petry don-petry commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The bot-comment retry sweep now knows what happened to the run it dispatched.

  • After a successful dispatch the sweep finds the run it created (the dev-lead caller stub's repository_dispatch run titled dev-lead-reviews-retry, created at or after the dispatch) and records it on the retry marker as run=<id>. It records only when exactly one candidate exists; otherwise the marker keeps today's time-based behaviour. The marker is edited, not re-posted, because the caller stub subscribes to issue_comment created only, so the edit starts no run in the lane.
  • On later scans it reads each recorded run. A run that completed as cancelled with no job having run a step is lost: it is not pending and does not count toward BOT_COMMENT_RETRY_MAX_ATTEMPTS.
  • A recorded run that is still queued or running now holds every comment on the PR, bounded by the pending window.
  • After a lost retry, the PR's next retry waits BOT_COMMENT_RETRY_LOST_BACKOFF_SEC (default: the 15-minute MIN_AGE).
  • BOT_COMMENT_RETRY_MAX_TOTAL still counts every marker, lost ones included, so a lane that keeps cancelling is bounded.
  • bcr_retry_decisions stays pure: the run states are passed in as an optional sixth argument, and all network calls are in the sweep.

Problem

Closes #2262. A retry's dispatched run joins the PR's concurrency lane, where GitHub keeps one pending run. When a newer event cancelled it before any step ran, the sweep still treated the marker as "queued or running" for 150 minutes and counted it as one of two attempts.

Reading the real runs for the issue's evidence changed the picture: on PR #2205 the retries cancelled each other. Run 38073395880 (the "CodeAnt Nitpicks" retry) was created at 17:50:45 and cancelled at 18:00:00, three seconds after the sweep posted the retry marker for the second comment at 17:59:57. The pending check was per comment, so the next scan dispatched for comment B while comment A's retry was still queued. That is why this change adds the PR-wide hold and the backoff: releasing a cancelled retry promptly with nothing else would turn the loss into a ping-pong up to MAX_TOTAL.

Risk

Medium. It changes when the sweep dispatches fix-bot-comment retries, in scripts/dev-lead-retry.sh and scripts/lib/bot-comment-retry.sh. Every unknown falls back to today's behaviour: a marker with no run=, a run missing from the map, an unreadable Actions API, a run cancelled after it started, a truncated listing. A non-numeric run= is ignored, and run ids are taken only from markers that pass the existing trusted-login rule. Older readers are unaffected: run= is a trailing attribute, and the unchanged library decides identically on markers that carry it.

Known limits, not addressed here:

  • A dispatch that creates no run at all is not detected; an empty listing cannot be told apart from a filter mismatch, and reading it as lost would duplicate live runs. So acceptance criterion 1 is met for a cancelled run and not for "never started".
  • Markers already on open PRs carry no run= and keep the time-based behaviour, as does any dispatch whose run cannot be paired (two retries reaching one repo within a few seconds).
  • Posting a retry marker still fires issue_comment.created into the lane. That is existing behaviour.
  • The bot-thread retry (dev-lead: unprocessed bot review threads are never retried — review-thread counterpart of #2017 #2046) has the same blind spot for its own cancelled run; bot-thread-retry.sh is untouched.
  • The sweep needs to read Actions runs in each target repo. A classic PAT with repo can; a fine-grained token needs Actions read. If it cannot, each affected scan logs a ::warning:: and keeps today's behaviour. Private target repos are unverified.
  • Pairing can still record another PR's run when two retries reach one repo within seconds and ours is listed later than both looks. The effect is bounded by BOT_COMMENT_RETRY_MAX_TOTAL.
  • The recording path has not run against a real dispatch yet. That needs a live check once a channel carries it.

Test plan

  • 19 new bats tests: 16 in tests/dev-lead/unit/test_bot_comment_retry.bats, 2 in test_dev_lead_retry.bats, 1 in test_intent_bot_comment_retry.bats. They cover a cancelled retry being re-dispatched, cancelled retries not exhausting the attempt limit, the hard bound, and a live run not being duplicated, at the pure-decision level and through the sweep with a stubbed gh. No existing test line is changed.
  • bats on the three touched files: 86/86, 36/36, 23/23 (run twice, by the implementing agent and again independently).
  • Counterfactual: with the scripts/ hunks reverse-applied, all 19 new tests fail and the 126 existing ones pass.
  • A wider run of 18 related suites: 611 passed, 0 failed.
  • shellcheck --severity=warning -x on both scripts: clean. scripts/check-retry-payload-fields.sh and scripts/check-duplicate-decls.sh: pass.
  • Read-only against the real API: the state reader returns cancelled-and-not-started for runs 38073395880 and 38077617157 and success for 38073354743; the pairing function picks exactly 38073395880 from the real listing for the evidence window.

Review follow-up (b816e34)

  • Backoff longer than the pending window (CodeAnt). bcr_retry_run_ids reads a retry's run inside the longer of the two windows, so a lost run past the pending window is still known lost and the backoff holds.
  • Skipped steps (CodeAnt). A job whose only steps were skipped no longer counts as started. A cancelled step, or one with no conclusion, still does: it may have begun.
  • Pairing the wrong run (CodeAnt, QA Lead). A run is recorded only when two looks in a row show the same single candidate. One look could record another PR's retry run that was listed before this dispatch's own. This narrows the race and does not close it: if our run is listed later than both looks, or never, the other run is still the only candidate. Added to the known limits below.
  • Lookup time (CodeAnt). BOT_COMMENT_RETRY_RUN_LOOKUP_DEADLINE_SEC (default 1200): once the scan has run that long, a dispatch records no run instead of waiting, so the waits cannot use up the workflow's 30-minute limit. The confirming look adds one wait (5 s by default) to each dispatch.
  • Real API data (QA Lead). tests/fixtures/bot-comment-retry/ holds what the Actions API returned on 2026-10-11 for PR feat: implement issue #1787 — calibrate solution-architect escalation so conforming changes are not escalated #2205's cancelled retry run 38073395880, its jobs, and the event=repository_dispatch listing around it, cut down to the fields read. The pairing and the run-state reader are tested against it. The server-side created= and event= filters were exercised by that read; the tests still use a fake gh that ignores them.
  • Cancelled before any job existed (QA Lead): a sweep-level test pins that it reads as lost.
  • Not done: the rollout check QA Lead suggests (compare [run] lines with dispatches for a few days after promotion and treat 0% as a failed rollout). It is a step for whoever promotes the channel.
  • 7 new tests; 5 fail on the previous scripts, and the fixture test and the "no job existed" test pass either way. test_bot_comment_retry 93/93, test_dev_lead_retry 36/36, test_intent_bot_comment_retry 23/23. shellcheck, check-retry-payload-fields.sh and check-duplicate-decls.sh: clean.

Rollback

Revert this PR. Markers that already carry run= are read by the older code exactly as before.

Monitoring

The dev-lead-retry workflow log: [run] … retry attempt N is Actions run <id> after a dispatch, [lost] … was cancelled before it started on a later scan, and the ::warning:: lines when a run cannot be read. Inert until the dev-lead channel tag moves onto it (next autocut to dev-lead/v139-next, then ring promotion); pr-review's own channel also runs this scan.

Interaction contract

  • N/A: no agentic role added or changed.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

…er holds the pending window or an attempt (#2262)

A fix-bot-comment retry joins the PR's concurrency lane, where GitHub keeps
one pending run, so a newer PR event cancels it before a step runs. The sweep
only saw its own marker: the cancelled retry stayed "pending" for 150 minutes
and used up one of the two attempts.

The sweep now records the run each dispatch created on the retry marker
(run=<id>, by editing the marker) and reads recorded runs back on later scans:

- a run cancelled with no job step run is lost: not pending, and not counted
  toward BOT_COMMENT_RETRY_MAX_ATTEMPTS. It still counts toward
  BOT_COMMENT_RETRY_MAX_TOTAL, and the PR's next retry waits out a short
  backoff, so a lane that keeps cancelling stays bounded;
- a run still queued or running holds every comment on the PR, so one
  comment's retry no longer cancels another's queued run (the PR #2205
  sequence);
- a run that cannot be paired with its dispatch (none, or more than one
  candidate) or cannot be read keeps the time-based behaviour. An API error is
  never read as "cancelled".

bcr_retry_decisions stays pure: run states are passed in as data. Markers
without run= parse as before, and an older parser reads a marker with run=
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@don-petry don-petry added the dev-lead:hands-off Exclude from dev-lead agent automation label Oct 11, 2026
@don-petry
don-petry requested a review from a team as a code owner October 11, 2026 03:51
@don-petry don-petry added the dev-lead:hands-off Exclude from dev-lead agent automation label Oct 11, 2026
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@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, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 24b9daa7-7120-413f-ae5c-d0039180858a

📥 Commits

Reviewing files that changed from the base of the PR and between 82461dc and b816e34.


📒 Files selected for processing (8)
  • scripts/dev-lead-retry.sh
  • scripts/lib/bot-comment-retry.sh
  • tests/dev-lead/unit/test_bot_comment_retry.bats
  • tests/dev-lead/unit/test_dev_lead_retry.bats
  • tests/dev-lead/unit/test_intent_bot_comment_retry.bats
  • tests/fixtures/bot-comment-retry/run-38073395880-jobs.json
  • tests/fixtures/bot-comment-retry/run-38073395880.json
  • tests/fixtures/bot-comment-retry/runs-listing-pr2205.json

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Comment thread scripts/lib/bot-comment-retry.sh Outdated
Comment thread scripts/lib/bot-comment-retry.sh Outdated
@codeant-ai

codeant-ai Bot commented Oct 11, 2026

Copy link
Copy Markdown

CodeAnt Nitpicks

2 code suggestions

1. When run listings lag, three serial sleeps add 15 seconds per dispatched PR; enough retries can exhaust the workflow’s 30-minute limit before later PRs are scanned.

Performance · scripts/dev-lead-retry.sh:880


2. A two-second lookback can match another PR’s retry before this dispatch appears; recording that run makes this PR’s later decisions depend on another PR’s state.

Race condition · scripts/dev-lead-retry.sh:1198

@codeant-ai

codeant-ai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

CodeAnt PR Risk: Medium Risk

  • The PR needs attention before merging: run lookup can attach another PR’s retry run to this marker.
  • Two matching looks do not rule out delayed visibility; the code acknowledges that an unrelated run may remain the sole candidate, skewing pending or lost decisions.

Assessed commit: b816e34ba9ec

@don-petry

Copy link
Copy Markdown
Collaborator Author

dev-lead is withholding action on this item.

It is labeled dev-lead:hands-off (deliberately kept off-limits to dev-lead (e.g. changes to the dev-lead automation itself)), so dev-lead will not pick it up while that label is present. This notice is posted once so the withhold is visible rather than looking like a stalled run.

To re-enable automated pickup: remove the dev-lead:hands-off label.

@donpetry-bot

Copy link
Copy Markdown
Contributor

Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-11T05:05:33Z.

@don-petry

Copy link
Copy Markdown
Collaborator Author

QA Lead — test-risk advisory

Risk tier: MEDIUM. This changes when automated retries are dispatched. If it goes wrong, the result is duplicate or cancelled agent runs, still capped by MAX_TOTAL. It touches no money, auth or data path, and every unknown falls back to today's time-based behaviour.

What I'd shore up (highest leverage first):

  • Pairing can pick the wrong run, and no test covers it. The fake gh returns the same run listing on every lookup. In production, a run for another PR can appear first, from pr-review's channel running this same scan in the same repo. On try 1 our run isn't visible yet; on try 2 only the other PR's run is. The code sees exactly one candidate and records the wrong id. If that run is later lost, this PR re-dispatches over its own queued retry. Add a test whose listing changes between tries. Then either guard against this (for example, re-check the count once after the pick) or list it under Known limits.
  • Server-side filters are never checked. The fake ignores created=>= and event=, so the pairing only works if GitHub's real filter and display_title behave as assumed. You already read the real listing for PR feat: implement issue #1787 — calibrate solution-architect escalation so conforming changes are not escalated #2205 once. Save that response as a fixture and test bcr_pick_dispatched_run and bcr_run_state against the real data instead of hand-built JSON.
  • A fallback that never ends looks like success. If pairing never works live, the feature does nothing and only [warn] lines show it. After promotion, compare the count of [run] lines with dispatches over a few days, and treat 0% as a failed rollout.
  • Edge case worth a test: a cancelled run whose jobs list has total_count: 0 (cancelled before any jobs were created). It should read as lost, and no test pins that today.

Escalate? No. The tests are thorough (19 new, with a counterfactual run and fail-safe defaults). The gaps above are worth closing but don't need to block the merge.

Advisory only — I comment, I do not change code. Ground: BMAD Test Architecture (bmad-tea v1.19.0). Opt out on this item with the qa-lead:hands-off label.

- bcr_retry_run_ids reads a retry's run inside the lost-retry backoff as well
  as the pending window. With a backoff longer than the pending window a lost
  run past the pending window was never read, so the backoff could not hold.
- bcr_run_state no longer counts a job whose only steps were skipped as
  started. A cancelled step, or one with no conclusion, still counts as run.
- record_bot_comment_retry_run records a run only when two looks in a row show
  the same single candidate. One look could record another PR's retry run that
  was listed before this dispatch's own. The second look narrows that race; a
  run of ours listed later than both looks still leaves the other as the only
  candidate.
- BOT_COMMENT_RETRY_RUN_LOOKUP_DEADLINE_SEC (default 1200): past it a dispatch
  records no run instead of waiting, so lookup waits cannot use up the retry
  workflow's 30-minute limit.
- Tests: 7 new, 5 of which fail on the previous scripts. One runs the pairing
  and the run-state reader against the Actions API's real answers for
  PR #2205's cancelled retry (tests/fixtures/bot-comment-retry/).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@don-petry
don-petry merged commit 0bb0d40 into main Oct 11, 2026
89 of 118 checks passed
@don-petry
don-petry deleted the fix/issue-2262-cancelled-bot-comment-retry branch October 11, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-lead:hands-off Exclude from dev-lead agent automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A bot-comment retry cancelled before it starts still holds the 150-minute pending window and uses up an attempt

2 participants