Skip to content

fix: missing/queued required checks didn't block PR merge#928

Merged
myakove merged 6 commits intomainfrom
fix/pr-merge-check-queued
Nov 20, 2025
Merged

fix: missing/queued required checks didn't block PR merge#928
myakove merged 6 commits intomainfrom
fix/pr-merge-check-queued

Conversation

@myakove
Copy link
Copy Markdown
Collaborator

@myakove myakove commented Nov 20, 2025

Fixed bug where PRs could merge when required checks were missing or queued.
Added caching for all_required_status_checks() to reduce overhead.
Fixed blocking PyGithub property accesses by wrapping in asyncio.to_thread.
Added 9 comprehensive tests for missing/queued check scenarios.

All 66 tests pass with 91% coverage maintained.

Summary by CodeRabbit

  • Tests

    • Expanded async test coverage for many status-check scenarios (missing, queued, failed, pending, mixed), added fixtures/mocks for commit statuses, and added safeguards to prevent shutdown delays in log-related tests.
  • Refactor

    • Consolidated required-status evaluation, added caching, and now retrieves commit checks and statuses in parallel to improve reliability and responsiveness.

✏️ Tip: You can customize this high-level summary in your review settings.

Fixed bug where PRs could merge when required checks were missing or queued.
Added caching for all_required_status_checks() to reduce overhead.
Fixed blocking PyGithub property accesses by wrapping in asyncio.to_thread.
Added 9 comprehensive tests for missing/queued check scenarios.

All 66 tests pass with 91% coverage maintained.
Fixed slow tests by mocking asyncio.wait() to avoid 30s timeouts:
- test_get_logs_page: 30.12s → <0.1s
- test_mcp_logging_configuration: 30.00s → <0.1s

Full test suite runtime: ~81s → ~21s (74% faster)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 20, 2025

Walkthrough

Adds commit-status-aware logic and caching to CheckRunHandler, parallelizes last-commit check_run/status fetches, updates method signatures and call sites to pass commit statuses, and expands async tests plus test-time asyncio.wait mocks to avoid shutdown delays.

Changes

Cohort / File(s) Summary
CheckRunHandler implementation
webhook_server/libs/handlers/check_run_handler.py
Adds private _all_required_status_checks cache; expands signatures to accept last_commit_statuses; updates required_check_failed_or_no_status, required_check_in_progress, all_required_status_checks, and get_branch_required_status_checks to consider CommitStatus results, cache branch-protection contexts, and use asyncio.to_thread for blocking repo/privacy calls.
Pull request handler
webhook_server/libs/handlers/pull_request_handler.py
Fetches last-commit check_runs and statuses in parallel via asyncio.gather and forwards last_commit_statuses to downstream checks.
CheckRunHandler tests
webhook_server/tests/test_check_run_handler.py
Adds extensive async tests and fixtures covering required checks detection, missing/queued/failed states, in-progress exclusions, commit-status integration, and message/token assertions.
Async wait mocks in tests
webhook_server/tests/test_log_api.py, webhook_server/tests/test_logging_separation.py
Replaces asyncio.wait with a mock that cancels tasks and returns immediately to avoid long shutdown waits during tests.
Pull request handler tests
webhook_server/tests/test_pull_request_handler.py
Extends last-commit mock to include get_statuses (returns an empty list in updated tests).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify all call sites were updated to pass the new last_commit_statuses parameter.
  • Inspect caching: _all_required_status_checks and any branch-protection context cache for correctness and thread-safety.
  • Confirm use of asyncio.to_thread only wraps truly blocking calls.
  • Check tests' asyncio.wait mocking doesn't mask real shutdown behavior.

Possibly related issues

  • Issue #929: Matches merge-blocking behavior changes — this PR incorporates CommitStatus into required-check detection and in-progress logic, addressing the same area.

Possibly related PRs

  • PR #808: Modifies CheckRunHandler methods (e.g., required_check_failed_or_no_status, get_branch_required_status_checks) and likely overlaps code paths changed here.
  • PR #804: Expands CheckRunHandler tests and required-status-check logic; closely related to the test additions and behavioral changes in this PR.

Suggested labels

size/L, commented-myakove

Suggested reviewers

  • rnetser
  • dbasunag

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'fix: missing/queued required checks didn't block PR merge' directly and clearly describes the main bug fix in the changeset—the core issue being addressed across CheckRunHandler, PullRequestHandler, and their tests.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pr-merge-check-queued

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.

@myakove-bot
Copy link
Copy Markdown
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
    "
    f"* Issue Creation: A tracking issue is created for this PR and will be closed when the PR is merged or closed
    "
    "* 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

📋 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)

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, or 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.

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

1 similar comment
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

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: 0

🧹 Nitpick comments (1)
webhook_server/tests/test_logging_separation.py (1)

39-45: Consider extracting the mock_wait utility to reduce duplication.

The mock_wait function is duplicated in both test_logging_separation.py (lines 39-45) and test_log_api.py (lines 644-650). Consider extracting this to a shared test utility module to follow DRY principles.

You could create a shared utility in webhook_server/tests/conftest.py:

async def mock_asyncio_wait(tasks, timeout=None, return_when=None):
    """Mock asyncio.wait to prevent blocking during test shutdown."""
    for task in tasks:
        task.cancel()
    return set(), tasks

Then import and use it in both test files:

from webhook_server.tests.conftest import mock_asyncio_wait

with patch("webhook_server.app.asyncio.wait", side_effect=mock_asyncio_wait):
    # test code
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfb349b and 93a7bf1.

📒 Files selected for processing (4)
  • webhook_server/libs/handlers/check_run_handler.py (5 hunks)
  • webhook_server/tests/test_check_run_handler.py (1 hunks)
  • webhook_server/tests/test_log_api.py (2 hunks)
  • webhook_server/tests/test_logging_separation.py (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-28T13:04:00.466Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2024-10-14T14:12:28.924Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1638-1639
Timestamp: 2024-10-14T14:12:28.924Z
Learning: In `webhook_server_container/libs/github_api.py`, it is acceptable for `self.repository.owner.email` to be `None` since internal commands can handle this case.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2025-10-30T00:18:06.176Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/github_api.py:111-118
Timestamp: 2025-10-30T00:18:06.176Z
Learning: In webhook_server/libs/github_api.py, when creating temporary directories or performing operations that need repository names, prefer using self.repository_name (from webhook payload, always available) over dereferencing self.repository.name or self.repository_by_github_app.name, which may be None. This avoids AttributeError and keeps the code simple and reliable.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
🧬 Code graph analysis (4)
webhook_server/libs/handlers/check_run_handler.py (1)
webhook_server/tests/conftest.py (2)
  • pull_request (106-107)
  • PullRequest (90-102)
webhook_server/tests/test_check_run_handler.py (1)
webhook_server/libs/handlers/check_run_handler.py (1)
  • required_check_failed_or_no_status (363-407)
webhook_server/tests/test_logging_separation.py (1)
webhook_server/tests/test_log_api.py (1)
  • mock_wait (645-650)
webhook_server/tests/test_log_api.py (2)
webhook_server/tests/test_logging_separation.py (1)
  • mock_wait (40-45)
webhook_server/tests/test_app.py (2)
  • client (36-38)
  • client (344-345)
🪛 Ruff (0.14.5)
webhook_server/tests/test_logging_separation.py

40-40: Unused function argument: timeout

(ARG001)


40-40: Unused function argument: return_when

(ARG001)

webhook_server/tests/test_log_api.py

645-645: Unused function argument: timeout

(ARG001)


645-645: Unused function argument: return_when

(ARG001)

🔇 Additional comments (17)
webhook_server/tests/test_logging_separation.py (1)

40-40: Static analysis false positive on unused parameters.

The timeout and return_when parameters are intentionally unused but required to match the asyncio.wait signature for proper mocking. These parameters ensure the mock can be used as a drop-in replacement.

webhook_server/tests/test_log_api.py (2)

645-645: Static analysis false positive on unused parameters.

Same as in test_logging_separation.py, these parameters are required to match the asyncio.wait signature for proper mocking.


644-667: LGTM - Test utility prevents blocking during shutdown.

The mock implementation correctly cancels all tasks and returns immediately, preventing the 30-second shutdown timeout mentioned in the PR objectives. The test assertions remain valid.

Note: This duplicates the mock from test_logging_separation.py. See the refactoring suggestion in that file to centralize this utility.

webhook_server/libs/handlers/check_run_handler.py (5)

41-41: LGTM - Cache attribute for required status checks.

The new cache attribute prevents redundant computation of required status checks across multiple invocations within the same webhook request handler.


367-374: LGTM - Core bug fix: Detects missing required checks.

This change addresses the PR's main objective by identifying required checks that haven't been created yet. The set difference correctly identifies checks in all_required_status_checks but not present in last_commit_check_runs.

This prevents PRs from being merged when required checks are missing or queued, as stated in the PR title: "fix: missing/queued required checks didn't block PR merge".


410-435: LGTM - Caching optimization with proper implementation.

The caching logic correctly:

  1. Returns cached value if available (line 411-412)
  2. Computes the full list of required checks
  3. Caches the result before returning (line 434)

The cache lifetime is scoped to the CheckRunHandler instance, which is created per webhook request, so no cache invalidation is needed.


440-440: LGTM - Prevents blocking on property access.

Using asyncio.to_thread to wrap the repository.private property access prevents blocking the event loop, consistent with the PR's goal of preventing blocking PyGithub property accesses.


454-456: LGTM - Async wrapper for nested property access.

The lambda wrapper inside asyncio.to_thread correctly handles the nested property access branch_protection.required_status_checks.contexts, maintaining consistency with the async pattern used throughout the file.

webhook_server/tests/test_check_run_handler.py (9)

640-663: LGTM - Comprehensive test for missing required checks.

This test validates the core bug fix: missing required checks (not yet created) are correctly detected and reported as "not started". The test correctly:

  • Mocks a scenario where TOX_STR is required but missing
  • Verifies VERIFIED_LABEL_STR (successful) doesn't appear in the error
  • Confirms the error message includes "not started"

665-692: LGTM - Test validates queued checks are treated as not started.

This test ensures that checks with conclusion=None (queued/in-progress state) are correctly identified as "not started" in the error message, preventing premature PR merges.


694-720: LGTM - Test validates failed check detection.

Ensures failed checks appear in the error message with "failed" status while successful checks are excluded.


722-748: LGTM - Test validates no errors when all checks succeed.

Correctly verifies that when all required checks are successful, an empty string is returned (no blocking errors).


750-779: LGTM - Test validates non-required checks are ignored.

Important edge case: ensures that missing checks that are not in the required list don't cause false positive errors.


781-807: LGTM - Test validates multiple missing checks detection.

Ensures all missing required checks appear in the error message, not just the first one found.


809-850: LGTM - Comprehensive test for mixed check states.

This test validates complex scenarios with checks in various states (missing, queued, failed, successful) to ensure the logic handles all combinations correctly.


852-880: LGTM - Test validates in-progress checks are excluded from failures.

Correctly ensures that checks being rerun (in progress) aren't reported as failed, avoiding false positive errors during check reruns.


882-907: LGTM - Test validates can-be-merged check is always ignored.

Important test ensuring the special can-be-merged check doesn't cause recursive blocking (it shouldn't block itself).

GitHub has two APIs for commit checks:
- Check Runs API (modern) - used by webhook server
- Statuses API (legacy) - used by external services like pre-commit.ci

Previously only Check Runs were validated, causing external service
checks (like "pre-commit.ci - pr") to show as "not started" even when
they passed.

Changes:
- Fetch both check runs AND statuses concurrently (asyncio.gather)
- Validate statuses alongside check runs in merge checks
- Map status states (success/pending/failure) to check outcomes
- Add last_commit_statuses parameter to validation methods

API optimization: 2 calls executed concurrently = same latency as 1 call

Fixes: PR #928 incorrectly reporting pre-commit.ci as "not started"
…oved

Previous commit added get_statuses() call to check_if_can_be_merged()
but test only mocked get_check_runs(). Added get_statuses mock
returning empty list to match production code behavior.
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

1 similar comment
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

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: 0

🧹 Nitpick comments (4)
webhook_server/tests/test_check_run_handler.py (1)

642-909: New tests give strong coverage for required-check edge cases; consider adding CommitStatus-focused cases

The new suite around required_check_failed_or_no_status (missing, queued, failed, mixed states, in-progress exclusions, and can-be-merged ignoring) accurately encodes the intended behavior and locks in the regression fix for missing/queued checks not blocking merges. One possible follow‑up improvement is to add a couple of tests that exercise the last_commit_statuses path directly (e.g., required checks represented only as CommitStatus with state="pending" or "failure"), to ensure the legacy status API behavior is equally protected.

webhook_server/libs/handlers/check_run_handler.py (3)

364-435: required_check_failed_or_no_status semantics correctly enforce missing/queued/failed required checks

The refactor to:

  • derive required_checks once from all_required_status_checks,
  • compute missing_required_checks across both check runs and legacy statuses,
  • treat queued / conclusion=None check runs and absent required checks as “not started”,
  • and classify non‑success CommitStatus states (pending → “not started”, failure/error → failed),

ensures that:

  • missing required checks now block merge,
  • queued check-runs and pending statuses also block merge,
  • and failures from either API surface are captured.

Two small polish opportunities that don’t affect correctness:

  • Avoid repeated await self.all_required_status_checks(...) inside the loop by reusing the local required_checks set.
  • When exclude_in_progress ends up empty, consider skipping the "Some check runs failed: ..." line altogether to avoid an empty “failed” message when everything is currently being rerun and is already reported via the in‑progress path.

Functionally this looks solid for the regression you’re fixing.


437-487: Caching of required status checks and branch protection contexts looks correct for per-PR handler usage

The introduction of _all_required_status_checks plus the existing _repository_private / _branch_required_status_checks caches neatly avoids repeated GitHub API calls:

  • all_required_status_checks now short‑circuits once computed and logs the combined list.
  • get_branch_required_status_checks defers all PyGithub property access (repository.private, get_branch, get_protection, .required_status_checks.contexts) into asyncio.to_thread, keeping the event loop non‑blocking.
  • Private repositories still skip branch protection lookup early, as before.

Assuming CheckRunHandler instances are scoped per webhook/PR (which matches how it’s constructed in PullRequestHandler), this caching is safe and beneficial. If you ever reuse a handler across PRs with different base refs, you may want an explicit invalidation strategy keyed by pull_request.base.ref, but that’s not required for the current design.


489-513: Including pending CommitStatus entries in required_check_in_progress closes the queued-check gap

Extending required_check_in_progress to:

  • take last_commit_statuses alongside check runs, and
  • add any required contexts with state == "pending" to check_runs_in_progress,

means queued/pending required checks reported only via the legacy status API now correctly block merge, aligned with the new tests. As above, you could micro‑optimize by reusing a cached required_checks set instead of awaiting all_required_status_checks inside the comprehensions, but behavior is correct as is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93a7bf1 and 0e19954.

📒 Files selected for processing (4)
  • webhook_server/libs/handlers/check_run_handler.py (7 hunks)
  • webhook_server/libs/handlers/pull_request_handler.py (3 hunks)
  • webhook_server/tests/test_check_run_handler.py (3 hunks)
  • webhook_server/tests/test_pull_request_handler.py (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:2089-2100
Timestamp: 2024-10-29T08:09:57.157Z
Learning: In `webhook_server_container/libs/github_api.py`, when the function `_keep_approved_by_approvers_after_rebase` is called, existing approval labels have already been cleared after pushing new changes, so there's no need to check for existing approvals within this function.
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.
📚 Learning: 2024-10-29T08:09:57.157Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:2089-2100
Timestamp: 2024-10-29T08:09:57.157Z
Learning: In `webhook_server_container/libs/github_api.py`, when the function `_keep_approved_by_approvers_after_rebase` is called, existing approval labels have already been cleared after pushing new changes, so there's no need to check for existing approvals within this function.

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2025-10-28T13:04:00.466Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.

Applied to files:

  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2024-10-29T10:42:50.163Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:925-926
Timestamp: 2024-10-29T10:42:50.163Z
Learning: In `webhook_server_container/libs/github_api.py`, the method `self._keep_approved_by_approvers_after_rebase()` must be called after removing labels when synchronizing a pull request. Therefore, it should be placed outside the `ThreadPoolExecutor` to ensure it runs sequentially after label removal.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-08T09:19:56.185Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-14T14:12:28.924Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1638-1639
Timestamp: 2024-10-14T14:12:28.924Z
Learning: In `webhook_server_container/libs/github_api.py`, it is acceptable for `self.repository.owner.email` to be `None` since internal commands can handle this case.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2025-10-30T00:18:06.176Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/github_api.py:111-118
Timestamp: 2025-10-30T00:18:06.176Z
Learning: In webhook_server/libs/github_api.py, when creating temporary directories or performing operations that need repository names, prefer using self.repository_name (from webhook payload, always available) over dereferencing self.repository.name or self.repository_by_github_app.name, which may be None. This avoids AttributeError and keeps the code simple and reliable.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
🧬 Code graph analysis (4)
webhook_server/tests/test_pull_request_handler.py (2)
webhook_server/tests/conftest.py (1)
  • github_webhook (111-131)
webhook_server/tests/test_check_run_handler.py (2)
  • get_check_runs (517-518)
  • get_check_runs (531-532)
webhook_server/tests/test_check_run_handler.py (1)
webhook_server/libs/handlers/check_run_handler.py (1)
  • required_check_failed_or_no_status (364-435)
webhook_server/libs/handlers/pull_request_handler.py (2)
webhook_server/tests/conftest.py (2)
  • github_webhook (111-131)
  • pull_request (106-107)
webhook_server/tests/test_check_run_handler.py (2)
  • get_check_runs (517-518)
  • get_check_runs (531-532)
webhook_server/libs/handlers/check_run_handler.py (1)
webhook_server/tests/conftest.py (2)
  • pull_request (106-107)
  • PullRequest (90-102)
🔇 Additional comments (5)
webhook_server/tests/test_check_run_handler.py (2)

553-559: Updated call to required_check_failed_or_no_status matches new signature

Passing last_commit_statuses=[] and check_runs_in_progress=[] keeps this test aligned with the new API while still verifying the original failure behavior; no issues here.


611-641: Addition of last_commit_statuses arg in required_check_in_progress tests is consistent

Extending the call to pass an empty last_commit_statuses list keeps the test focused on check-runs-only behavior and matches the new handler signature.

webhook_server/tests/test_pull_request_handler.py (1)

667-705: last_commit mock now correctly satisfies parallel check-runs/statuses fetch

Updating test_check_if_can_be_merged_approved to patch github_webhook.last_commit with get_check_runs and get_statuses returning empty lists matches the new asyncio.gather(... get_check_runs(), ... get_statuses()) behavior in check_if_can_be_merged. This keeps the test focused on the approval/labeling path without introducing network calls or unrelated failures.

webhook_server/libs/handlers/pull_request_handler.py (2)

956-965: Parallelizing last-commit check-runs and statuses fetch is a good non-blocking improvement

Using:

_check_runs, _statuses = await asyncio.gather(
    asyncio.to_thread(lambda: list(self.github_webhook.last_commit.get_check_runs())),
    asyncio.to_thread(lambda: list(self.github_webhook.last_commit.get_statuses())),
)

reduces two blocking GitHub API calls to one concurrent operation while keeping them off the event loop. Any exceptions bubble into the surrounding try/except where the merge check is already handled as a failure, so this remains safe from a correctness perspective.


975-999: Wiring last_commit_statuses into required-check evaluation correctly closes the merge gap

Passing both last_commit_check_runs and last_commit_statuses into:

  • required_check_in_progress(... last_commit_statuses=last_commit_statuses), and
  • required_check_failed_or_no_status(... last_commit_statuses=last_commit_statuses, check_runs_in_progress=check_runs_in_progress),

ensures merge eligibility now considers:

  • in-progress/pending required checks (from either API),
  • missing required checks,
  • and failed required checks,

before deciding whether to add the CAN_BE_MERGED_STR label or fail the merge check. This directly addresses the bug where missing/queued required checks previously allowed merges.

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

5 similar comments
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/check-can-merge

GitHub Status API returns all historical statuses for a commit, not just
the latest update. This caused checks like 'pre-commit.ci - pr' to show
as "not started" even when the latest status was "success".

Changes:
- Filter statuses by highest ID per context (most recent)
- Remove last_commit_statuses from required_check_in_progress (Status API has no in_progress state)
- Clean up excessive debug logging (removed 13 verbose logs, kept 4 useful ones)
- Add comprehensive test validating status+check run combined validation
- Add debug logging for status fetching in pull_request_handler

Fixes: Status checks showing incorrect state
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e19954 and 43792f9.

📒 Files selected for processing (3)
  • webhook_server/libs/handlers/check_run_handler.py (7 hunks)
  • webhook_server/libs/handlers/pull_request_handler.py (3 hunks)
  • webhook_server/tests/test_check_run_handler.py (4 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:2089-2100
Timestamp: 2024-10-29T08:09:57.157Z
Learning: In `webhook_server_container/libs/github_api.py`, when the function `_keep_approved_by_approvers_after_rebase` is called, existing approval labels have already been cleared after pushing new changes, so there's no need to check for existing approvals within this function.
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.
📚 Learning: 2025-10-28T13:04:00.466Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.

Applied to files:

  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2024-10-29T08:09:57.157Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:2089-2100
Timestamp: 2024-10-29T08:09:57.157Z
Learning: In `webhook_server_container/libs/github_api.py`, when the function `_keep_approved_by_approvers_after_rebase` is called, existing approval labels have already been cleared after pushing new changes, so there's no need to check for existing approvals within this function.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-29T10:42:50.163Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:925-926
Timestamp: 2024-10-29T10:42:50.163Z
Learning: In `webhook_server_container/libs/github_api.py`, the method `self._keep_approved_by_approvers_after_rebase()` must be called after removing labels when synchronizing a pull request. Therefore, it should be placed outside the `ThreadPoolExecutor` to ensure it runs sequentially after label removal.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-08T09:19:56.185Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-14T14:12:28.924Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1638-1639
Timestamp: 2024-10-14T14:12:28.924Z
Learning: In `webhook_server_container/libs/github_api.py`, it is acceptable for `self.repository.owner.email` to be `None` since internal commands can handle this case.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2025-10-30T00:18:06.176Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/github_api.py:111-118
Timestamp: 2025-10-30T00:18:06.176Z
Learning: In webhook_server/libs/github_api.py, when creating temporary directories or performing operations that need repository names, prefer using self.repository_name (from webhook payload, always available) over dereferencing self.repository.name or self.repository_by_github_app.name, which may be None. This avoids AttributeError and keeps the code simple and reliable.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
🧬 Code graph analysis (3)
webhook_server/tests/test_check_run_handler.py (4)
webhook_server/tests/test_pull_request_handler.py (1)
  • mock_pull_request (135-155)
webhook_server/tests/test_runner_handler.py (1)
  • mock_pull_request (53-63)
webhook_server/libs/handlers/check_run_handler.py (1)
  • required_check_failed_or_no_status (364-459)
webhook_server/tests/conftest.py (1)
  • pull_request (106-107)
webhook_server/libs/handlers/pull_request_handler.py (2)
webhook_server/tests/conftest.py (2)
  • github_webhook (111-131)
  • pull_request (106-107)
webhook_server/tests/test_check_run_handler.py (2)
  • get_check_runs (524-525)
  • get_check_runs (538-539)
webhook_server/libs/handlers/check_run_handler.py (1)
webhook_server/tests/conftest.py (1)
  • PullRequest (90-102)
🔇 Additional comments (11)
webhook_server/tests/test_check_run_handler.py (2)

561-563: LGTM!

The test correctly updates the call signature to match the new method signature with last_commit_statuses and check_runs_in_progress parameters.


649-917: Excellent test coverage for required check validation.

These new tests comprehensively cover various scenarios for required check validation including missing, queued, failed, and successful checks. The test suite properly validates edge cases like in-progress checks being excluded from failures and the can-be-merged check being ignored.

webhook_server/libs/handlers/check_run_handler.py (7)

42-42: LGTM!

The cache field follows the established pattern used for _branch_required_status_checks and will help reduce overhead from repeated status check calculations.


364-370: LGTM!

The updated signature properly extends the method to handle commit statuses (legacy Statuses API) alongside check runs, addressing the bug described in the PR objectives.


373-419: LGTM!

The status processing logic correctly handles the legacy Statuses API alongside the Check Runs API. The deduplication by highest status.id is the correct approach for handling multiple status updates for the same context, and the logic properly maps status states (pending, failure, error) to the corresponding check outcomes.


420-440: LGTM!

The updated check run processing correctly handles the case where the same check exists as both a CheckRun and a CommitStatus by prioritizing successful statuses, preventing duplicate error reporting.


461-487: LGTM!

The caching logic is well-implemented and will reduce overhead from repeated calculations of required status checks during PR processing.


492-492: LGTM!

Wrapping PyGithub property accesses in asyncio.to_thread is the correct approach to prevent blocking the event loop during synchronous I/O operations. These changes align with the PR objectives to avoid blocking calls.

Also applies to: 506-508


513-530: LGTM!

The updated signature and added comment correctly clarify that only Check Runs (not Statuses) have an "in_progress" state, which is an important distinction between the two GitHub APIs.

webhook_server/libs/handlers/pull_request_handler.py (2)

959-973: LGTM!

The parallel fetching of check runs and statuses using asyncio.gather is an excellent optimization that reduces latency by performing two API calls concurrently. The logging provides good observability for debugging.


984-987: LGTM!

The updated method calls correctly pass the new parameters to align with the modified signatures in check_run_handler.py. The distinction between which methods receive last_commit_statuses is appropriate based on the functionality of each method.

Also applies to: 997-1002

Comment thread webhook_server/tests/test_check_run_handler.py Outdated
Comment thread webhook_server/tests/test_check_run_handler.py
Address CodeRabbit AI review comments:
- Add base.ref attribute to mock_pull_request fixture to prevent potential test failures
- Add .id attributes to CommitStatus mocks for proper status deduplication
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/reprocess

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Nov 20, 2025

/approve
/verified

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: 0

🧹 Nitpick comments (2)
webhook_server/tests/test_check_run_handler.py (2)

54-59: Mock PullRequest fixture now correctly models base.ref and is used where needed

Setting mock_pr.base.ref = "main" aligns this fixture with how branch-required checks access PR data and addresses the earlier risk of AttributeError when pull_request.base.ref is used. Using this fixture explicitly in test_required_check_failed_or_no_status_with_commit_statuses keeps that test realistic without over-configuring unrelated attributes. You could optionally migrate some ad‑hoc Mock() PRs in this file to reuse this fixture, but that's not required.

Also applies to: 921-923


651-919: New required-check tests comprehensively cover missing/queued/failed/in‑progress/ignored scenarios

The added tests around required_check_failed_or_no_status exercise the key branches:

  • missing vs queued vs failed required checks,
  • all-success and non-required checks,
  • multiple missing checks, mixed states,
  • in-progress reruns excluded, and CAN_BE_MERGED_STR ignored even on failure.

These assertions line up with the documented behavior of the handler and should guard well against regressions in merge gating logic. If you want to trim repetition later, you could factor shared setup (e.g., constructing required lists and simple mocks) into small helpers or a pytest.mark.parametrize table, but the current explicit style is perfectly fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43792f9 and e87f12c.

📒 Files selected for processing (1)
  • webhook_server/tests/test_check_run_handler.py (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.
🧬 Code graph analysis (1)
webhook_server/tests/test_check_run_handler.py (4)
webhook_server/tests/test_pull_request_handler.py (1)
  • mock_pull_request (135-155)
webhook_server/tests/test_runner_handler.py (1)
  • mock_pull_request (53-63)
webhook_server/tests/test_owners_files_handler.py (1)
  • mock_pull_request (24-29)
webhook_server/libs/handlers/check_run_handler.py (1)
  • required_check_failed_or_no_status (364-459)
🔇 Additional comments (2)
webhook_server/tests/test_check_run_handler.py (2)

563-565: Updated required_check_failed_or_no_status call matches the new async signature

Passing last_commit_check_runs, last_commit_statuses, and check_runs_in_progress explicitly keeps this test in sync with the handler’s expanded parameters while still focusing only on the failed required-check path. This looks correct and future-proof.


921-979: Commit-status integration test accurately captures the PR‑928 regression scenario

The test_required_check_failed_or_no_status_with_commit_statuses case does a good job simulating PR‑928: it mixes check runs with legacy commit statuses, marks only the pre‑commit status as required, ensures .id is present for deduplication, and verifies that:

  • the required status-based check (pre-commit.ci - pr) is treated as successful,
  • only the queued required check-run (VERIFIED_LABEL_STR) is reported as “not started”, and
  • non-required statuses like CodeRabbit are ignored.

This directly validates the intended behavior for missing/queued required checks when some checks only exist as commit statuses.

@myakove myakove merged commit 586f4eb into main Nov 20, 2025
8 of 9 checks passed
@myakove myakove deleted the fix/pr-merge-check-queued branch November 20, 2025 20:21
@myakove-bot
Copy link
Copy Markdown
Collaborator

New container for ghcr.io/myk-org/github-webhook-server:latest published

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