Skip to content

Comments

feat: block can-be-merged when unresolved review conversations exist#998

Open
rnetser wants to merge 6 commits intomainfrom
feat/issue-997-required-conversation-resolution
Open

feat: block can-be-merged when unresolved review conversations exist#998
rnetser wants to merge 6 commits intomainfrom
feat/issue-997-required-conversation-resolution

Conversation

@rnetser
Copy link
Collaborator

@rnetser rnetser commented Feb 24, 2026

Summary

  • Adds unresolved review conversation check to the can-be-merged workflow
  • Reads required_conversation_resolution from existing branch-protection config (no new config options)
  • Uses GitHub GraphQL API with cursor-based pagination to fetch all review threads
  • Shows clickable URLs to each unresolved thread in the check run output
  • Includes outdated threads with (outdated) marker

Changes

  • webhook_server/libs/github_api.pyget_unresolved_review_threads() GraphQL method with pagination
  • webhook_server/libs/handlers/pull_request_handler.py — integration in check_if_can_be_merged()
  • webhook_server/tests/test_pull_request_handler.py — 11 new test cases

Test plan

  • All 1288 tests pass
  • 90.52% code coverage (meets 90% threshold)
  • ruff check + format clean
  • No new mypy errors

Summary by CodeRabbit

  • New Features
    • Branch-protection can require conversation resolution: PRs are blocked when unresolved review threads exist. Failure output now lists each thread (file, line, outdated flag, optional link). Supports per-repository and global overrides.
  • Tests
    • Expanded tests covering conversation-resolution flows (enabled/disabled), multiple/unresolved threads, pagination, empty comments, and error handling.

Use GitHub GraphQL API to check for unresolved review threads when
branch-protection.required_conversation_resolution is enabled.
Includes cursor-based pagination, comment URLs in failure output,
and outdated thread markers.

Closes #997
@myakove-bot
Copy link
Collaborator

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: Disabled for this repository
  • Pre-commit Checks: pre-commit runs automatically if .pre-commit-config.yaml exists
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified
  • Labels: All label categories are enabled (default configuration)

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)
  • /regenerate-welcome - Regenerate this welcome message

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest build-container - Rebuild and test container image
  • /retest python-module-install - Test Python package installation
  • /retest pre-commit - Run pre-commit hooks and checks
  • /retest conventional-title - Validate commit message format
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 1 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No WIP, hold, conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

@qodo-code-review
Copy link

Review Summary by Qodo

Block PR merge when unresolved review conversations exist

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add unresolved review conversation check to can-be-merged workflow
• Implement GitHub GraphQL API with cursor-based pagination for threads
• Display clickable URLs and outdated markers in check output
• Read configuration from existing branch-protection settings
Diagram
flowchart LR
  Config["branch-protection config<br/>required_conversation_resolution"]
  GraphQL["GitHub GraphQL API<br/>reviewThreads query"]
  Pagination["Cursor-based pagination<br/>fetch all threads"]
  Filter["Filter unresolved threads<br/>include outdated marker"]
  Output["Check run output<br/>with thread URLs"]
  Merge["can-be-merged check<br/>pass/fail decision"]
  
  Config --> GraphQL
  GraphQL --> Pagination
  Pagination --> Filter
  Filter --> Output
  Output --> Merge
Loading

Grey Divider

File Changes

1. webhook_server/libs/github_api.py ✨ Enhancement +117/-0

Add GraphQL method to fetch unresolved review threads

• Add httpx import for async HTTP requests to GitHub GraphQL API
• Parse required_conversation_resolution from branch-protection config (global and
 repository-level)
• Implement get_unresolved_review_threads() method with GraphQL query and cursor-based pagination
• Extract thread metadata (path, line, URL, outdated flag) from first comment in each unresolved
 thread

webhook_server/libs/github_api.py


2. webhook_server/libs/handlers/pull_request_handler.py ✨ Enhancement +26/-7

Integrate unresolved thread check into merge validation

• Refactor parallel API calls to conditionally fetch unresolved threads based on feature flag
• Add unresolved thread check in check_if_can_be_merged() after other validations
• Format failure output with thread count, file paths, line numbers, URLs, and outdated markers
• Prevent PR merge when unresolved threads exist and feature is enabled

webhook_server/libs/handlers/pull_request_handler.py


3. webhook_server/tests/test_pull_request_handler.py 🧪 Tests +573/-0

Add comprehensive tests for review thread validation

• Add httpx import for mocking HTTP responses
• Add repository_full_name to mock webhook fixture
• Add required_conversation_resolution flag to mock webhook
• Implement 11 new test cases covering feature disabled, no threads, multiple threads, pagination,
 empty comments, API errors, GraphQL errors, and PR not found scenarios

webhook_server/tests/test_pull_request_handler.py


View more (1)
4. webhook_server/tests/test_schema_validator.py Formatting +6/-1

Format boolean fields list

• Reformat boolean_fields list for improved readability with one field per line

webhook_server/tests/test_schema_validator.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 24, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. required_conversation_resolution not validated📘 Rule violation ⛨ Security
Description
required_conversation_resolution is read from config and assigned to a bool-typed attribute
without validating that the config value is actually boolean. A malformed config (e.g., string/dict)
can silently change behavior or cause runtime errors, violating the requirement to validate external
inputs.
Code

webhook_server/libs/github_api.py[R737-751]

+        # Read required_conversation_resolution from branch-protection config
+        _global_bp: dict[str, Any] = self.config.get_value(value="branch-protection", return_on_none={})
+        _global_bp = _global_bp if isinstance(_global_bp, dict) else {}
+        _repo_bp: dict[str, Any] = self.config.get_value(
+            value="branch-protection", return_on_none={}, extra_dict=repository_config
+        )
+        _repo_bp = _repo_bp if isinstance(_repo_bp, dict) else {}
+        # Repository-level overrides global; default is False
+        self.required_conversation_resolution: bool
+        if "required_conversation_resolution" in _repo_bp:
+            self.required_conversation_resolution = _repo_bp["required_conversation_resolution"]
+        elif "required_conversation_resolution" in _global_bp:
+            self.required_conversation_resolution = _global_bp["required_conversation_resolution"]
+        else:
+            self.required_conversation_resolution = False
Evidence
PR Compliance ID 6 requires validating external inputs; the new code reads
required_conversation_resolution from config dictionaries and assigns it directly to
self.required_conversation_resolution without any isinstance(..., bool) check or explicit error.
Since Config.get_value() returns raw values without type enforcement, this setting can be
non-boolean at runtime.

Rule 6: Generic: Security-First Input Validation and Data Handling
webhook_server/libs/github_api.py[737-751]
webhook_server/libs/config.py[132-153]

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

## Issue description
`required_conversation_resolution` is pulled from user-controlled config and assigned directly to a `bool` attribute without validating the value type.
## Issue Context
`Config.get_value()` returns raw YAML values without type enforcement, so a misconfigured value (e.g., `&amp;amp;amp;amp;amp;quot;true&amp;amp;amp;amp;amp;quot;`, `1`, `{...}`) can cause unexpected truthiness/behavior.
## Fix Focus Areas
- webhook_server/libs/github_api.py[737-751]

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



Remediation recommended

2. Inconsistent default behavior🐞 Bug ✓ Correctness
Description
This PR defaults required_conversation_resolution to False when the key is absent, but the
repository settings defaults treat required_conversation_resolution as True. This inconsistency can
make can-be-merged succeed (and possibly auto-merge) with unresolved conversations when users rely
on defaults rather than explicitly setting the key.
Code

webhook_server/libs/github_api.py[R744-751]

+        # Repository-level overrides global; default is False
+        self.required_conversation_resolution: bool
+        if "required_conversation_resolution" in _repo_bp:
+            self.required_conversation_resolution = _repo_bp["required_conversation_resolution"]
+        elif "required_conversation_resolution" in _global_bp:
+            self.required_conversation_resolution = _global_bp["required_conversation_resolution"]
+        else:
+            self.required_conversation_resolution = False
Evidence
GithubWebhook uses a hardcoded False default when the key is missing, while
github_repository_settings.DEFAULT_BRANCH_PROTECTION and get_repo_branch_protection_rules imply a
default of True for required_conversation_resolution when branch-protection is not specified.

webhook_server/libs/github_api.py[744-751]
webhook_server/utils/github_repository_settings.py[36-43]
webhook_server/utils/github_repository_settings.py[204-208]

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

## Issue description
The default for `required_conversation_resolution` differs between the webhook merge eligibility logic (False) and the branch-protection defaults (True). This can lead to surprising behavior when users omit the key.
### Issue Context
`DEFAULT_BRANCH_PROTECTION` is used to establish default branch-protection semantics elsewhere; this PR introduces a similar semantic gate for merging but with a different default.
### Fix Focus Areas
- webhook_server/libs/github_api.py[737-751]
- webhook_server/utils/github_repository_settings.py[36-43]
- webhook_server/utils/github_repository_settings.py[204-208]
### Suggested approach
- Decide on the intended default (likely match `DEFAULT_BRANCH_PROTECTION`).
- Implement the default in one place (import a constant or call a helper) so future changes don’t diverge.
- Add/adjust tests to cover the “key absent” scenario.

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


3. API calls not counted 🐞 Bug ✓ Correctness
Description
The new GraphQL call uses httpx directly, bypassing the existing PyGithub CountingRequester
instrumentation used for token_spend/rate-limit diagnostics. This will under-report GitHub API usage
in logs/context when conversation resolution enforcement is enabled.
Code

webhook_server/libs/github_api.py[R907-923]

+        async with httpx.AsyncClient() as client:
+            while True:
+                variables: dict[str, Any] = {
+                    "owner": owner,
+                    "repo": repo,
+                    "prNumber": pr_number,
+                    "cursor": cursor,
+                }
+                response = await client.post(
+                    "https://api.github.com/graphql",
+                    json={"query": query, "variables": variables},
+                    headers={
+                        "Authorization": f"Bearer {self.token}",
+                        "Content-Type": "application/json",
+                    },
+                    timeout=30.0,
+                )
Evidence
Token spend metrics are computed from the wrapped PyGithub requester call count, but
get_unresolved_review_threads uses httpx.AsyncClient to call the GitHub GraphQL endpoint, so those
calls are invisible to requester_wrapper.count.

webhook_server/libs/github_api.py[907-923]
webhook_server/libs/github_api.py[190-203]

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

## Issue description
GraphQL requests made via `httpx` are not reflected in the existing GitHub API call counting/token_spend metrics, reducing the accuracy of operational diagnostics.
### Issue Context
The webhook uses a `CountingRequester` wrapper around PyGithub’s requester to compute `ctx.token_spend`. Direct `httpx` calls bypass this instrumentation.
### Fix Focus Areas
- webhook_server/libs/github_api.py[861-960]
- webhook_server/libs/github_api.py[190-203]
### Suggested approach
- Option A: Use the existing PyGithub requester (or wrapper) to POST to `/graphql` so the counter increments.
- Option B: Introduce an explicit counter/metric in `WebhookContext` for external GitHub calls and increment it for GraphQL usage.
- Add a small unit test asserting metrics include the GraphQL call when the feature is enabled.

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


4. Validator misses repo rules 🐞 Bug ⛯ Reliability
Description
The config validation utility validates only root-level branch-protection, not repository-level
branch-protection, even though this PR reads repo-level required_conversation_resolution overrides.
This can let invalid repo overrides slip through validation and later affect merge behavior at
runtime.
Code

webhook_server/libs/github_api.py[R737-743]

+        # Read required_conversation_resolution from branch-protection config
+        _global_bp: dict[str, Any] = self.config.get_value(value="branch-protection", return_on_none={})
+        _global_bp = _global_bp if isinstance(_global_bp, dict) else {}
+        _repo_bp: dict[str, Any] = self.config.get_value(
+            value="branch-protection", return_on_none={}, extra_dict=repository_config
+        )
+        _repo_bp = _repo_bp if isinstance(_repo_bp, dict) else {}
Evidence
GithubWebhook reads repository-level branch-protection from a repo-specific config dict, but
ConfigValidator only calls _validate_branch_protection for the root config and does not validate
repo_config['branch-protection'] in _validate_single_repository.

webhook_server/libs/github_api.py[737-743]
webhook_server/tests/test_schema_validator.py[95-97]
webhook_server/tests/test_schema_validator.py[145-206]

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

## Issue description
`test_schema_validator.py` (used as a CLI validator per README) doesn’t validate repository-level `branch-protection`, but the webhook now reads `repositories.&amp;amp;amp;amp;amp;lt;repo&amp;amp;amp;amp;amp;gt;.branch-protection.required_conversation_resolution`.
### Issue Context
Users may rely on the validator to catch type errors; without repo-level validation, malformed repo overrides can cause incorrect merge gating.
### Fix Focus Areas
- webhook_server/tests/test_schema_validator.py[132-206]
### Suggested approach
- In `_validate_single_repository`, if `&amp;amp;amp;amp;amp;quot;branch-protection&amp;amp;amp;amp;amp;quot; in repo_config`, call `_validate_branch_protection` for that object.
- Update error messages to include repository context (e.g., `repositories.&amp;amp;amp;amp;amp;lt;name&amp;amp;amp;amp;amp;gt;.branch-protection.strict`).

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds branch-protection-backed conversation-resolution: new public required_conversation_resolution on GithubWebhook, a GraphQL helper get_unresolved_review_threads(pr_number), and integration into PR merge checks to optionally block merges when unresolved review threads exist.

Changes

Cohort / File(s) Summary
Branch-protection & GraphQL API
webhook_server/libs/github_api.py
Add required_conversation_resolution (resolved from repo/global defaults) and implement get_unresolved_review_threads(pr_number) using httpx.AsyncClient to query GitHub GraphQL with pagination, filter resolved/outdated threads, and propagate HTTP/GraphQL/lookup errors.
Merge-check integration
webhook_server/libs/handlers/pull_request_handler.py
Extend check_if_can_be_merged() to optionally fetch unresolved review threads in parallel with check runs/statuses when required_conversation_resolution is enabled; construct and append a conversation_failure block listing unresolved threads (path, line, isOutdated, url) to failure output.
Tests — pull request handler & API
webhook_server/tests/test_pull_request_handler.py
Add tests for conversation-resolution flows (disabled/enabled; none/single/multiple unresolved threads) and extensive unit tests for get_unresolved_review_threads() covering pagination, filtering, empty data, HTTP/GraphQL errors, and missing PR/repo cases.
Tests — schema validator formatting
webhook_server/tests/test_schema_validator.py
Reformat boolean_fields list in _validate_single_repository to multiple lines (trailing comma) with no semantic change.

Sequence Diagram

sequenceDiagram
    participant Handler as Pull Request Handler
    participant GH as GithubWebhook
    participant Checks as Check Runs/Statuses Service
    participant API as GitHub GraphQL API

    Handler->>Handler: check_if_can_be_merged(pr)
    Handler->>GH: read required_conversation_resolution
    alt enabled
        par Parallel fetch
            Handler->>Checks: fetch last commit check runs
            Handler->>Checks: fetch commit statuses
            Handler->>GH: get_unresolved_review_threads(pr_number)
            GH->>API: GraphQL query (paginated) for reviewThreads
            API-->>GH: thread pages (path,line,isOutdated,url)
            GH-->>Handler: unresolved threads list
        and
        end
        Handler->>Handler: evaluate unresolved threads
        alt threads found
            Handler->>Handler: build conversation_failure block with thread details
            Handler->>Handler: append to failure_output (merge blocked)
        else no threads
            Handler->>Handler: continue other merge checks
        end
    else disabled
        par Parallel fetch
            Handler->>Checks: fetch last commit check runs
            Handler->>Checks: fetch commit statuses
        end
        Handler->>Handler: continue other merge checks
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

size/XL, can-be-merged

Suggested reviewers

  • myakove
  • dbasunag
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: adding a check to prevent merging when unresolved review conversations exist, which aligns with the primary objective across all modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/issue-997-required-conversation-resolution

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webhook_server/tests/test_pull_request_handler.py`:
- Around line 1095-1358: Several tests assign the literal "test-token" to
pull_request_handler.github_webhook.token; replace those hardcoded tokens with
the shared TEST_GITHUB_TOKEN constant to satisfy secret-scanning and project
conventions. Update every occurrence where the test sets
pull_request_handler.github_webhook.token = "test-token" (within tests calling
GithubWebhook.get_unresolved_review_threads) to use TEST_GITHUB_TOKEN instead,
and ensure the test module imports or references the TEST_GITHUB_TOKEN constant
available in the test suite.
- Around line 809-821: Replace positional boolean arguments in patch.object
calls with the explicit new= keyword to silence Ruff FBT003 and clarify intent;
for example change patch.object(mock_pull_request, "mergeable", True) to
patch.object(mock_pull_request, "mergeable", new=True) and
patch.object(pull_request_handler.github_webhook,
"required_conversation_resolution", False) to
patch.object(pull_request_handler.github_webhook,
"required_conversation_resolution", new=False); apply the same change for all
occurrences in this test file (e.g., around the mock_pull_request patches,
patches on pull_request_handler.github_webhook, and any other patch.object calls
that currently pass booleans positionally).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b4d9ba and 3438058.

📒 Files selected for processing (4)
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/tests/test_schema_validator.py

- Validate required_conversation_resolution as bool via bool() cast
- Use DEFAULT_BRANCH_PROTECTION default (True) instead of hardcoded False
- Use TEST_GITHUB_TOKEN constant instead of hardcoded test tokens
- Use new= keyword for boolean patch.object calls (Ruff FBT003)
Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webhook_server/libs/github_api.py`:
- Around line 933-938: The code assumes data["data"]["repository"] is present
but GitHub can return repository: null; update the block that assigns pr_data
(currently: pr_data = data["data"]["repository"]["pullRequest"]) to first check
repository = data["data"].get("repository") and if repository is None raise a
ValueError with a clear message including self.repository_full_name and
pr_number to indicate missing repository or insufficient token access, then
proceed to fetch pullRequest and reviewThreads as before (refer to symbols
pr_data, review_threads, repository_full_name, pr_number).
- Around line 738-755: The code currently uses bool(...) which treats strings
like "false" as True; change the assignment logic around
required_conversation_resolution to perform explicit type validation: when
reading _repo_bp[_bp_key] or _global_bp[_bp_key], check isinstance(value, bool)
and only accept it if True; if the value is not a bool, log a warning (use the
instance logger, e.g., self.logger.warning) indicating invalid branch-protection
value for required_conversation_resolution and fall back to _bp_default (or the
next valid scope), rather than casting; ensure this validation is applied to
both repository-level and global-level reads so that DEFAULT_BRANCH_PROTECTION
remains the safe default.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3438058 and ed482cd.

📒 Files selected for processing (2)
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_pull_request_handler.py

- Use isinstance(value, bool) instead of bool() cast to prevent
  "false" string being treated as True
- Log warning on invalid config values and fall back to default
- Handle repository: null in GraphQL response with clear ValueError
- Add test for null repository response
Copy link

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webhook_server/libs/github_api.py`:
- Line 741: Replace the module-level assert that checks _bp_raw_default with a
runtime check that always runs: in the code around symbol _bp_raw_default (used
when building DEFAULT_BRANCH_PROTECTION), change the assert
isinstance(_bp_raw_default, bool) into an explicit conditional that raises a
TypeError (or ValueError) with a clear message like
"DEFAULT_BRANCH_PROTECTION[<key>] must be bool" when the check fails, so the
invariant holds even when Python runs with -O; ensure the error message
references _bp_key/_bp_raw_default to aid debugging.
- Around line 738-761: The warning uses self.log_prefix which may not be set
when _repo_data_from_config is first called; change the log prefix access to use
getattr(self, "log_prefix", "") in the branch-protection validation block so the
warning won't raise if log_prefix is missing. Specifically update the code
around required_conversation_resolution assignment and the logger.warning call
inside the for loop in _repo_data_from_config to use getattr(self, "log_prefix",
"") (same pattern as used later at lines with other warnings) so misconfigured
branch-protection values emit a warning instead of crashing.

In `@webhook_server/tests/test_pull_request_handler.py`:
- Around line 804-1033: The four tests
test_can_be_merged_conversation_resolution_disabled,
test_can_be_merged_no_unresolved_threads,
test_can_be_merged_unresolved_threads_present, and
test_can_be_merged_multiple_unresolved_threads duplicate the same large patch
block; extract that repeated patch setup into a single helper (either a
`@contextmanager` or a pytest fixture) that applies the common patch.object calls
against PullRequestHandler instance (including patches for
_check_if_pr_approved, _check_labels_for_can_be_merged,
labels_handler._add_label/_remove_label,
owners_file_handler.owners_data_for_changed_files, github_webhook.minimum_lgtm,
check_run_handler.set_check_in_progress, required_check_in_progress,
required_check_failed_or_no_status, labels_handler.wip_or_hold_labels_exists,
labels_handler.pull_request_labels_names, github_webhook.last_commit) and accept
parameters for the varying pieces
(github_webhook.required_conversation_resolution,
github_webhook.get_unresolved_review_threads return value, and which label mock
to expose); then update each test to call the helper and use the returned mocks
(e.g., mock_add_label, mock_remove_label, mock_get_threads,
mock_set_check_failure) so each test only specifies the small variations and
assertions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed482cd and ecda5fe.

📒 Files selected for processing (2)
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_pull_request_handler.py

- Use getattr(self, "log_prefix", "") to prevent AttributeError when
  _repo_data_from_config runs before log_prefix is set
- Replace assert isinstance() with explicit TypeError raise to ensure
  validation is not stripped by python -O
Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webhook_server/libs/github_api.py`:
- Around line 760-764: The warning logs an incorrect fallback value
(_bp_default) when a repo-level value (_bp_val) is invalid but a valid
global/current value is already applied; update the logger.warning call (the
call using self.logger.warning and log_prefix) to include the actual
effective/current value that will be used instead of _bp_default (read whichever
variable holds the currently applied value before override, e.g., the config
field or local "current" variable) so the message reports the true fallback
rather than the literal _bp_default.
- Around line 969-974: Replace the current warning-and-break behavior when
page_info["endCursor"] is null with a fail-fast exception: instead of logging
via self.logger.warning(...) and breaking, raise a ValueError (or KeyError) that
includes context (self.log_prefix, pr_number and page_info) so callers cannot
receive partial results; update the code around variables cursor and page_info
to throw the exception when hasNextPage is True but endCursor is None, ensuring
the error propagates to the caller.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecda5fe and cb0f729.

📒 Files selected for processing (1)
  • webhook_server/libs/github_api.py

- Warning message now shows kept current value instead of hardcoded default
- Null cursor with hasNextPage=True now raises ValueError instead of
  returning partial data that could allow incorrect merge approval
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 25, 2026
Reduce ~80 lines of duplication across 4 can-be-merged conversation
resolution tests by extracting shared patch.object setup into a
_can_be_merged_patch_context contextmanager helper.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants