Skip to content

feat(maintenance): auto-dismiss stale CHANGES_REQUESTED bot reviews#3744

Merged
mabry1985 merged 1 commit into
mainfrom
fix/auto-dismiss-stale-bot-reviews
May 24, 2026
Merged

feat(maintenance): auto-dismiss stale CHANGES_REQUESTED bot reviews#3744
mabry1985 merged 1 commit into
mainfrom
fix/auto-dismiss-stale-bot-reviews

Conversation

@mabry1985
Copy link
Copy Markdown
Contributor

@mabry1985 mabry1985 commented May 24, 2026

Summary

Closes #3732. Adds a maintenance check that detects and dismisses bot CHANGES_REQUESTED reviews that have been superseded by later COMMENTED/APPROVED reviews on subsequent commits, when CI is green on the current head.

Why

GitHub branch protection only clears a CHANGES_REQUESTED review when the reviewer dismisses it explicitly OR submits a fresh APPROVED. Our review bots routinely re-review fix commits with COMMENTED (acknowledging the fix without escalating), which does NOT clear the blocking state.

Today (session), we hit this on three PRs (#3660, #3691, plus the crew dispatch flow) and had to manually dismiss each blocking review. This check eliminates that toil.

Detection criteria — all must hold

  1. PR is OPEN, mergeStateStatus: BLOCKED, reviewDecision: CHANGES_REQUESTED
  2. The blocking review's author is a [bot] user
  3. The blocking review's commit is NOT the current head
  4. The same bot has posted a later COMMENTED or APPROVED review
  5. All CI checks on head are COMPLETED + SUCCESS/SKIPPED/NEUTRAL (no FAILURE, no IN_PROGRESS)

When all five hold, dismiss with a message citing the follow-up review and head SHA.

Test plan

7 new unit tests in apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts:

  • Happy path: dismiss when superseded by COMMENTED on later commit
  • Skip when CHANGES_REQUESTED is on current head (not stale)
  • Skip when follow-up is from a different bot
  • Skip when blocking review is from a human
  • Skip when CI has a FAILURE on head
  • Skip when CI is still IN_PROGRESS (wait for stable state)
  • Skip when PR is not BLOCKED or has no CHANGES_REQUESTED decision

Full server suite: 3449/3449 pass.

Summary by CodeRabbit

  • New Features

    • Added a new maintenance check that automatically dismisses outdated blocking bot reviews on pull requests. The check runs when a blocking review from a bot has been superseded by a newer approval from the same bot and all required CI status checks pass (full tier).
  • Tests

    • Added comprehensive unit test suite validating dismissal conditions and edge cases.

Review Change Stack

Closes #3732. PRs get permanently BLOCKED when protoquinn[bot] or
coderabbitai[bot] leave a CHANGES_REQUESTED review on commit A, the
issues get fixed on commit B, and the bot re-reviews B as COMMENTED
instead of APPROVED. COMMENTED doesn't clear CHANGES_REQUESTED, so the
PR sits indefinitely until a maintainer manually dismisses.

Adds a new full-tier MaintenanceCheck that runs every 6h. For each open
PR with mergeStateStatus=BLOCKED and reviewDecision=CHANGES_REQUESTED:

1. Group reviews by bot author (login ends with `[bot]`).
2. Find any CHANGES_REQUESTED whose commit is NOT the current head.
3. Confirm the same bot has a later COMMENTED or APPROVED review.
4. Confirm all CI checks on head are COMPLETED + SUCCESS/SKIPPED.

When all four hold, dismiss the stale review with a message citing the
follow-up review and head SHA. The PR transitions out of BLOCKED on the
next reviewDecision recomputation.

Repo coords come from GITHUB_REPO_OWNER / GITHUB_REPO_NAME env vars,
defaulting to protoLabsAI/protoMaker so a fresh install works.

7/7 new tests cover:
  - Happy path: dismiss when superseded by COMMENTED on later commit
  - Skip: CHANGES_REQUESTED is on current head (not stale)
  - Skip: follow-up is from a different bot
  - Skip: blocking review is from a human (no `[bot]` suffix)
  - Skip: CI has a failure on head
  - Skip: CI still IN_PROGRESS (wait for stable state)
  - Skip: PR is not BLOCKED or has no CHANGES_REQUESTED decision

Full server suite: 3449/3449.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR adds a new maintenance check that automatically dismisses stale bot CHANGES_REQUESTED reviews when the same bot has posted a follow-up review on a later commit and CI is green. The check is implemented in a new file, integrated into the maintenance orchestrator, and covered by a comprehensive test suite with eight test scenarios.

Changes

Stale bot review dismissal check

Layer / File(s) Summary
Check implementation with GitHub API helpers
apps/server/src/services/maintenance/checks/auto-dismiss-stale-bot-reviews-check.ts
AutoDismissStaleBotReviewsCheck scans up to 50 open PRs via gh, filters to those with mergeStateStatus: BLOCKED and reviewDecision: CHANGES_REQUESTED, groups reviews by bot login, identifies stale CHANGES_REQUESTED reviews not on the current head, verifies the same bot posted a later COMMENTED or APPROVED review on a newer commit, validates CI checks are green with at least one completed check (rejects incomplete/queued), and dismisses each stale review via GitHub API while collecting MaintenanceIssue entries.
Orchestrator registration and module wiring
apps/server/src/services/maintenance.module.ts
Module imports AutoDismissStaleBotReviewsCheck, reads repo owner/name from environment variables with defaults (protoLabsAI/protoMaker), instantiates the check, wraps it with MaintenanceCheck that iterates over context.projectPaths, aggregates dismissed-review counts, and registers the result with maintenanceOrchestrator alongside existing checks.
Unit test coverage of dismissal and guard scenarios
apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts
Vitest suite with buildExec mock helper that records gh command calls and maps them to pre-canned responses. Tests verify: dismissal of stale CHANGES_REQUESTED when followed by COMMENTED on different commit with green CI, and non-dismissal guards when review is on current head, when no follow-up exists from bot, when blocking review is from human, when CI has failures, when CI is still in progress, and when PR is not BLOCKED.

Sequence Diagram(s)

sequenceDiagram
  participant Check as AutoDismissStaleBotReviewsCheck
  participant GitHub as GitHub API (gh)
  participant Result as MaintenanceIssue[]
  
  Check->>GitHub: listOpenPRs() - fetch up to 50 open PRs
  GitHub-->>Check: PR list with mergeStateStatus and reviewDecision
  
  Check->>Check: filter BLOCKED + CHANGES_REQUESTED
  
  loop per PR
    Check->>GitHub: listReviews() - fetch PR review history
    GitHub-->>Check: all reviews ordered by submitted_at
    
    Check->>Check: processPR - identify stale bot CHANGES_REQUESTED
    Check->>Check: verify later COMMENTED/APPROVED from same bot
    Check->>Check: allChecksGreen() - validate CI status
    
    alt all conditions met
      Check->>GitHub: dismiss via gh api - remove stale review
      GitHub-->>Check: dismissal success
      Check->>Result: record MaintenanceIssue
    else condition failed
      Check->>Check: skip PR
    end
  end
  
  Check-->>Result: return aggregated issues
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A bot review grows old, its wisdom fades fast,
When follow-up comments prove issues are past,
Green CI declares victory, the fix is complete,
So dismiss the old verdict—let the PR merge sweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a maintenance check to auto-dismiss stale bot CHANGES_REQUESTED reviews, which is the primary focus of all file changes.
Linked Issues check ✅ Passed The PR implements Option B from #3732: a maintenance check that dismisses stale bot CHANGES_REQUESTED reviews when a follow-up COMMENTED/APPROVED exists and CI is green, with seven unit tests covering the acceptance criteria.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the auto-dismiss maintenance check: module wiring, check implementation, and comprehensive unit tests with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auto-dismiss-stale-bot-reviews

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: one or more packages not found in the registry.


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

@mabry1985 mabry1985 enabled auto-merge (squash) May 24, 2026 10:49
@github-actions
Copy link
Copy Markdown
Contributor

Code Review — ? finding(s)

Async review running parallel to CodeRabbit. Findings are advisory; not all are merge blockers.

protoLabs Code Review Report

  • Generated: 2026-05-24T10:49:31Z
  • Git head: d273728f15e50dac2a14816c5c6137d4dcfac92f
  • Features mapped: 3
  • Findings: 0

No findings recorded.

@mabry1985
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts (1)

14-14: ⚡ Quick win

Use relative import for server-internal class in unit test

For this internal server implementation, prefer a relative path import to keep tests aligned with server-internal boundary conventions.

Based on learnings, "for tests verifying internal server implementation classes, import via relative paths within apps/server rather than shared/public-style aliases."

🤖 Prompt for 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.

In
`@apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts`
at line 14, Replace the aliased import of AutoDismissStaleBotReviewsCheck with a
relative-path import that points to the server-internal implementation used by
this unit test; update the import line importing AutoDismissStaleBotReviewsCheck
so it uses a relative path within the apps/server tree instead of the '`@/`...'
alias to keep the test aligned with server-internal boundaries.
🤖 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 `@apps/server/src/services/maintenance.module.ts`:
- Around line 203-204: Replace the hardcoded fallback values for repoOwner and
repoName (the constants currently assigned in maintenance.module.ts) so they are
sourced only from configuration/settings or environment without embedded
defaults; read them via the app configuration provider or process.env (but do
not provide 'protoLabsAI'/'protoMaker' as fallbacks), validate that the values
exist at startup (throw or log and fail fast if missing), and update any code
that references repoOwner/repoName to rely on the validated configuration values
rather than hardcoded defaults.

In
`@apps/server/src/services/maintenance/checks/auto-dismiss-stale-bot-reviews-check.ts`:
- Around line 149-153: The follow-up selection currently only checks state and
timestamp and can match a later review on the same commit; update the followUp
predicate (the const followUp = list.find(...) block) to also require the
follow-up review be for a different commit than latestStale by comparing commit
identifiers (e.g. r.commit_id or r.commit_sha) to
latestStale.commit_id/latestStale.commit_sha and treat missing values safely
(use nullish coalescing) so the condition becomes: state is COMMENTED/APPROVED,
submitted_at is later, and the commit id/sha is different from latestStale.

---

Nitpick comments:
In
`@apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts`:
- Line 14: Replace the aliased import of AutoDismissStaleBotReviewsCheck with a
relative-path import that points to the server-internal implementation used by
this unit test; update the import line importing AutoDismissStaleBotReviewsCheck
so it uses a relative path within the apps/server tree instead of the '`@/`...'
alias to keep the test aligned with server-internal boundaries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e4306b12-90fc-438e-8070-51db3b567f50

📥 Commits

Reviewing files that changed from the base of the PR and between d9aab6e and d2cbf56.

📒 Files selected for processing (3)
  • apps/server/src/services/maintenance.module.ts
  • apps/server/src/services/maintenance/checks/auto-dismiss-stale-bot-reviews-check.ts
  • apps/server/tests/unit/services/maintenance/auto-dismiss-stale-bot-reviews-check.test.ts

Comment on lines +203 to +204
const repoOwner = process.env.GITHUB_REPO_OWNER || 'protoLabsAI';
const repoName = process.env.GITHUB_REPO_NAME || 'protoMaker';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid hardcoded repo defaults in server maintenance wiring

Line 203-204 hardcodes repo owner/name fallbacks. In this path, repo/workflow coordinates should come from configuration only (env/settings), not embedded defaults.

As per coding guidelines, "apps/server/src/**/*.{ts,tsx} must never hardcode workflow-specific values ... These must come from settings or configuration."

🤖 Prompt for 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.

In `@apps/server/src/services/maintenance.module.ts` around lines 203 - 204,
Replace the hardcoded fallback values for repoOwner and repoName (the constants
currently assigned in maintenance.module.ts) so they are sourced only from
configuration/settings or environment without embedded defaults; read them via
the app configuration provider or process.env (but do not provide
'protoLabsAI'/'protoMaker' as fallbacks), validate that the values exist at
startup (throw or log and fail fast if missing), and update any code that
references repoOwner/repoName to rely on the validated configuration values
rather than hardcoded defaults.

Comment on lines +149 to +153
const followUp = list.find(
(r) =>
(r.state === 'COMMENTED' || r.state === 'APPROVED') &&
(r.submitted_at ?? '').localeCompare(latestStale.submitted_at ?? '') > 0
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Follow-up review must prove commit progression before dismissing

At Line 149, the follow-up check only validates review type + later timestamp. This can dismiss a still-valid CHANGES_REQUESTED if the bot later comments on the same commit. Add a commit guard so the follow-up is on a different (newer) commit than the stale review.

Proposed fix
       const followUp = list.find(
         (r) =>
           (r.state === 'COMMENTED' || r.state === 'APPROVED') &&
-          (r.submitted_at ?? '').localeCompare(latestStale.submitted_at ?? '') > 0
+          (r.submitted_at ?? '').localeCompare(latestStale.submitted_at ?? '') > 0 &&
+          !!r.commit_id &&
+          r.commit_id !== latestStale.commit_id
       );
🤖 Prompt for 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.

In
`@apps/server/src/services/maintenance/checks/auto-dismiss-stale-bot-reviews-check.ts`
around lines 149 - 153, The follow-up selection currently only checks state and
timestamp and can match a later review on the same commit; update the followUp
predicate (the const followUp = list.find(...) block) to also require the
follow-up review be for a different commit than latestStale by comparing commit
identifiers (e.g. r.commit_id or r.commit_sha) to
latestStale.commit_id/latestStale.commit_sha and treat missing values safely
(use nullish coalescing) so the condition becomes: state is COMMENTED/APPROVED,
submitted_at is later, and the commit id/sha is different from latestStale.

@mabry1985 mabry1985 merged commit d58c620 into main May 24, 2026
7 checks passed
@mabry1985 mabry1985 deleted the fix/auto-dismiss-stale-bot-reviews branch May 24, 2026 10: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.

bug(reviews): stale CHANGES_REQUESTED from protoquinn blocks PRs even after follow-up COMMENTED reviews on fix commits

1 participant