Skip to content

feat(labels): add configurable labels with enable/disable and custom colors#978

Merged
myakove merged 19 commits intomainfrom
feature/configurable-labels-976
Jan 15, 2026
Merged

feat(labels): add configurable labels with enable/disable and custom colors#978
myakove merged 19 commits intomainfrom
feature/configurable-labels-976

Conversation

@myakove
Copy link
Copy Markdown
Collaborator

@myakove myakove commented Jan 13, 2026

Summary

Implements configurable labels feature allowing users to:

  • Enable/disable specific label categories via enabled-labels config
  • Customize label colors via CSS3 color names in colors config
  • Maintain full backward compatibility (no config = all labels enabled)

Configuration

labels:
  enabled-labels:
    - verified
    - hold
    - wip
    - needs-rebase
    - has-conflicts
    - can-be-merged
    - size
    - branch
    - cherry-pick
    - automerge
  colors:
    hold: red
    verified: green
    approved-: limegreen

Important

reviewed-by labels (approved-*, lgtm-*, changes-requested-*, commented-*) are always enabled and cannot be disabled - they are the source of truth for the approval system.

Changes

  • Add labels schema to schema.yaml (global + per-repo levels)
  • Add enabled_labels and label_colors config loading in github_api.py
  • Add is_label_enabled() method in labels_handler.py
  • Update _get_label_color() to use configured colors
  • Add validation for enabled-labels with warning for invalid categories
  • Fix infinite recursion bug in _get_custom_pr_size_thresholds()
  • Use ALL_LABELS_DICT for DEFAULT_LABEL_COLORS to eliminate duplication
  • Add comprehensive tests (9 new tests)
  • Update examples/config.yaml with labels examples

Test plan

  • All 1147 tests pass
  • 90.34% code coverage (meets 90% requirement)
  • Pre-commit hooks pass (ruff, mypy, flake8)
  • Manual testing with labels config

Closes #976

Summary by CodeRabbit

  • New Features

    • Global and per-repository label configuration (enable/disable categories, custom colors, repo overrides).
    • Welcome-message regeneration command to rebuild or update PR welcome comments.
    • PR size labeling with configurable thresholds.
    • Safer log rollover to prevent logger crashes.
  • Behavior Changes

    • Label operations respect enabled categories (reviewed-by always active); color resolution uses configured and safe defaults.
    • Idempotent label/title/command flows and refined clone/fetch behavior for tags and PRs.
  • Tests

    • Expanded test coverage for schema, labels, welcome regeneration, cloning, size labels, commands, logging, and rollover.
  • Documentation

    • README updated with label configuration guidance.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 13, 2026

Walkthrough

Adds configurable label support: global and per-repository labels config with enabled-labels and colors. Schema validation enforces allowed categories; reviewed-by labels remain always enabled. Config merges into GithubWebhook.enabled_labels and GithubWebhook.label_colors; label handlers, welcome regeneration, and PR flows honor these settings.

Changes

Cohort / File(s) Summary
Config schema & examples
examples/config.yaml, webhook_server/config/schema.yaml
Add top-level and repositories.* labels schema (enabled-labels, colors) with enum validation and reviewed-by always enabled.
Config loading & validation
webhook_server/libs/config.py, webhook_server/libs/github_api.py
Load, coerce and validate global+repo labels config; merge into `GithubWebhook.enabled_labels: set[str]
Label logic, thresholds & colors
webhook_server/libs/handlers/labels_handler.py, webhook_server/utils/constants.py, webhook_server/tests/test_labels_handler.py
Add is_label_enabled(); async get_size/add_size_label; _add_label returns bool; rework color resolution to prefer configured exact → prefix → size → defaults; add STATIC_PR_SIZE_THRESHOLDS, DEFAULT_LABEL_COLORS, LABEL_CATEGORY_MAP, CONFIGURABLE_LABEL_CATEGORIES.
Welcome/comment flows & commands
webhook_server/libs/handlers/pull_request_handler.py, webhook_server/libs/handlers/issue_comment_handler.py, tests
Modularize welcome content, add regenerate_welcome_message and /regenerate-welcome command; gate title/verify edits on actual label-change indicators.
Git clone / fetch flow
webhook_server/libs/github_api.py, webhook_server/tests/test_github_api.py
Change clone/fetch to targeted fetches and shallow clone: PRs fetch base then PR ref; pushes clone only for tags (checkout_ref); adjust error handling and logging.
Schema & config tests
webhook_server/tests/test_config_schema.py
Add tests for valid, invalid, and empty enabled-labels (global and repo).
Constants & label naming
webhook_server/utils/constants.py, multiple handlers/tests
Rename CHERRY_PICKED_LABEL_PREFIXCHERRY_PICKED_LABEL; add COMMAND_REGENERATE_WELCOME_STR, DEFAULT_LABEL_COLORS, LABEL_CATEGORY_MAP, CONFIGURABLE_LABEL_CATEGORIES; update usages.
Owners diff robustness
webhook_server/libs/handlers/owners_files_handler.py
Fallback from three-dot to two-dot git diff when merge-base missing; improve logging.
Safe rotating handler & logging
webhook_server/utils/safe_rotating_handler.py, webhook_server/tests/test_safe_rotating_handler.py, webhook_server/utils/helpers.py
Add SafeRotatingFileHandler to suppress rollover OSErrors and patch simple_logger; add tests ensuring rollover resilience.
Utilities, typing & small API updates
webhook_server/utils/app_utils.py, webhook_server/app.py, webhook_server/utils/*, scripts/generate_changelog.py, tests
Tighten/adjust type annotations (IPv4Network
Docs & misc
README.md, .pre-commit-config.yaml, pyproject.toml, webhook_server/libs/handlers/check_run_handler.py
README expanded with label docs/examples; minor pre-commit formatting; mypy tightened for generics; guarded PyPI token masking update in check-run text.

Sequence Diagram(s)

sequenceDiagram
  participant Config as ConfigLoader
  participant Webhook as GithubWebhook
  participant Labels as LabelsHandler
  participant PR as PullRequestHandler
  participant Git as Git/GitHub

  Config->>Webhook: load global + per-repo `labels` config
  Webhook->>Webhook: merge configs -> enabled_labels, label_colors
  Webhook->>Labels: provide enabled_labels & label_colors
  PR->>Labels: request add/remove label for PR event
  Labels->>Labels: is_label_enabled(label) -> allow/deny
  alt allowed
    Labels->>Labels: resolve color (configured exact → prefix → size → default)
    Labels->>Git: create/update label with chosen color
    Labels->>PR: return boolean indicating change
    PR->>PR: perform title/welcome/verify updates only if change
  else denied
    Labels-->>PR: skip (no-change)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

verified

Suggested reviewers

  • rnetser
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR introduces several changes beyond the core label configuration feature: async refactoring (get_size, add_size_label), git diff fallback, mypy strictness increase, logging improvements (SafeRotatingFileHandler), and IP network type refinements. These appear to be supporting improvements rather than core scope. Clarify whether async refactoring, logging handler patches, git diff fallback, mypy strictness, and IP type updates are dependencies of label configuration or separate improvements that should be in a distinct PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 71.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(labels): add configurable labels with enable/disable and custom colors' directly and clearly summarizes the main changes—introducing configurable label enable/disable functionality and custom color support.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from #976: configurable enable/disable (except reviewed-by), customizable colors via CSS3, backward compatibility, schema validation, label integration across handlers, async refactoring, and comprehensive tests.

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

✨ 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 feature/configurable-labels-976

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

myakove-bot commented Jan 13, 2026

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: Enabled categories: automerge, branch, can-be-merged, cherry-pick, has-conflicts, needs-rebase, size, verified, wip

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /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, 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
  • 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 Jan 13, 2026

/hold

…colors

- Add labels schema to schema.yaml (global + per-repo levels)
- Add enabled_labels and label_colors config loading in github_api.py
- Add is_label_enabled() method in labels_handler.py
- Update _get_label_color() to use configured colors
- Add validation for enabled-labels with warning for invalid categories
- Fix infinite recursion bug in _get_custom_pr_size_thresholds()
- Use ALL_LABELS_DICT for DEFAULT_LABEL_COLORS to eliminate duplication
- Add comprehensive tests for label configuration
- Update examples/config.yaml with labels examples

Note: reviewed-by labels (approved-*, lgtm-*, etc.) are always enabled
and cannot be disabled as they are the source of truth for the approval system.

Closes #976
@myakove myakove removed the hold label Jan 13, 2026
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/hold

1 similar comment
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/hold

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

🤖 Fix all issues with AI agents
In @webhook_server/tests/test_config_schema.py:
- Around line 645-680: Add two additional test cases alongside
test_labels_configuration_schema to cover edge cases: one that writes a config
with an invalid label category in "labels":"enabled-labels" (e.g.,
"invalid-category") and asserts that constructing Config() raises a validation
error or the schema rejects it, and another that writes a config with an empty
"labels":"enabled-labels" array and asserts the resulting Config() behavior
(either accepted with empty list or defaults applied). Use the same helpers
(create_temp_config_dir_and_data, monkeypatch to set WEBHOOK_SERVER_DATA_DIR)
and reference the Config class to load the config; ensure cleanup with
shutil.rmtree in finally blocks.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 811908e and 1f23d80.

📒 Files selected for processing (7)
  • examples/config.yaml
  • webhook_server/config/schema.yaml
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_config_schema.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/utils/constants.py
🧰 Additional context used
📓 Path-based instructions (7)
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Internal methods in webhook_server/libs/ can change freely - return types can change (e.g., Anybool), method signatures can be modified without deprecation

Files:

  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/libs/github_api.py
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/tests/test_config_schema.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/libs/github_api.py
webhook_server/libs/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/handlers/**/*.py: Do NOT use defensive checks for architecture guarantees like repository_data which is ALWAYS set before handlers instantiate
Handler classes must implement __init__(self, github_webhook, ...) and process_event(event_data) pattern
Handlers must use self.github_webhook.unified_api for all GitHub operations, not direct PyGithub calls
Use ctx.start_step(), ctx.complete_step(), ctx.fail_step() for structured webhook execution tracking

Files:

  • webhook_server/libs/handlers/labels_handler.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_config_schema.py
  • webhook_server/tests/test_labels_handler.py
**/*.{yaml,yml}

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain backward compatibility for user-facing configuration files (config.yaml, .github-webhook-server.yaml) - configuration schema changes must support old formats or provide migration

Files:

  • examples/config.yaml
  • webhook_server/config/schema.yaml
webhook_server/config/schema.yaml

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/config/schema.yaml: Mask sensitive data in logs by default (set mask-sensitive-data: true in configuration)
Configuration schema changes should be tested with uv run pytest webhook_server/tests/test_config_schema.py -v

Files:

  • webhook_server/config/schema.yaml
webhook_server/libs/github_api.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/github_api.py: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification
Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., user.node_id, user.type, sender) - let KeyError surface legitimate bugs
Repository cloning for check_run events should skip with early exit when action != 'completed'
Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Files:

  • webhook_server/libs/github_api.py
🧠 Learnings (11)
📚 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/labels_handler.py
  • webhook_server/libs/github_api.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/tests/test_config_schema.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/utils/constants.py
  • webhook_server/tests/test_config_schema.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/config/schema.yaml
  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Code coverage must be 90% or higher - new code without tests fails CI

Applied to files:

  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to **/*.{yaml,yml} : Maintain backward compatibility for user-facing configuration files (`config.yaml`, `.github-webhook-server.yaml`) - configuration schema changes must support old formats or provide migration

Applied to files:

  • webhook_server/config/schema.yaml
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Access configuration using `Config(repository='org/repo-name')` and `config.get_value('setting-name', default_value)`

Applied to files:

  • webhook_server/libs/github_api.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/github_api.py
📚 Learning: 2024-11-21T16:10:01.777Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 624
File: webhook_server_container/libs/github_api.py:2067-2101
Timestamp: 2024-11-21T16:10:01.777Z
Learning: In `webhook_server_container/libs/github_api.py`, keep the `max_owners_files` limit as a hardcoded value rather than making it configurable through the config.

Applied to files:

  • webhook_server/libs/github_api.py
🧬 Code graph analysis (3)
webhook_server/tests/test_config_schema.py (1)
webhook_server/libs/config.py (2)
  • Config (11-142)
  • root_data (33-50)
webhook_server/tests/test_labels_handler.py (1)
webhook_server/libs/handlers/labels_handler.py (3)
  • _add_label (132-166)
  • is_label_enabled (49-107)
  • _get_label_color (180-218)
webhook_server/libs/github_api.py (1)
webhook_server/libs/config.py (1)
  • get_value (99-120)
🪛 Ruff (0.14.10)
webhook_server/libs/handlers/labels_handler.py

140-140: Logging statement uses f-string

(G004)

webhook_server/libs/github_api.py

668-669: Logging statement uses f-string

(G004)

🔇 Additional comments (16)
webhook_server/config/schema.yaml (2)

86-118: LGTM! Well-structured global labels configuration schema.

The schema correctly:

  • Defines enabled-labels as an enum array matching CONFIGURABLE_LABEL_CATEGORIES
  • Uses additionalProperties: false to prevent invalid keys
  • Documents that reviewed-by labels cannot be disabled
  • Maintains backward compatibility (optional block)

335-367: Repository-level labels schema mirrors global structure correctly.

The per-repository schema appropriately duplicates the global labels structure, enabling repository-specific overrides while maintaining consistent validation.

examples/config.yaml (2)

36-68: Excellent user-facing documentation for labels configuration.

The example clearly demonstrates:

  • All 10 configurable label categories
  • CSS3 color names for both static labels and dynamic prefixes (e.g., approved-)
  • The behavior when labels config is unset (all enabled)
  • That reviewed-by labels cannot be disabled

This will help users understand the feature quickly.


189-196: Good example of repository-level label override.

Shows a focused subset (verified, hold, size) with a custom color override, demonstrating how repository config takes precedence.

webhook_server/utils/constants.py (2)

78-80: Clean approach using alias to avoid duplication.

DEFAULT_LABEL_COLORS = ALL_LABELS_DICT maintains a single source of truth. Since both are used read-only throughout the codebase, the alias is safe.


82-95: Well-defined configurable categories constant.

The set correctly enumerates all 10 configurable label categories matching the schema enum. The comment explaining why reviewed-by is excluded (cannot be disabled) is helpful for maintainability.

webhook_server/libs/github_api.py (2)

651-674: Correct labels configuration loading and validation.

The implementation properly:

  • Merges global and repository configs (repo overrides global)
  • Validates enabled-labels against CONFIGURABLE_LABEL_CATEGORIES
  • Logs warnings for invalid categories without failing (forward compatible)
  • Sets enabled_labels = None when unconfigured (all labels enabled semantic)

One minor observation: the or {} on lines 652 and 654 handles both None and empty dict returns, which is defensive but appropriate here.


661-671: Behavior confirmed: empty enabled-labels array is intentional, not an edge case requiring handling.

Tests explicitly validate that enabled-labels: [] produces an empty set, disabling all configurable labels while preserving reviewed-by labels (test_labels_handler.py lines 1228–1233, test_reviewed_by_always_enabled). This is differentiated from "not configured" (None), which enables all labels. No changes needed.

webhook_server/libs/handlers/labels_handler.py (4)

49-107: Well-implemented label enablement logic.

The is_label_enabled method correctly handles:

  • reviewed-by labels (always enabled, cannot be disabled)
  • None means all labels enabled (backward compatible)
  • Static labels mapped to categories
  • Dynamic labels (size/, branch-, cherry-pick-) checked by prefix

One detail on line 103: CHERRY_PICKED_LABEL_PREFIX is checked with == while CHERRY_PICK_LABEL_PREFIX uses startswith. This is correct since CherryPicked is an exact label name, not a prefix pattern.


139-141: Correct guard for disabled labels.

The early return when is_label_enabled(label) is False prevents disabled labels from being added. The debug log helps with troubleshooting.


180-218: Color resolution logic with correct priority.

The _get_label_color method follows the right priority:

  1. Custom colors (exact match)
  2. Custom colors (prefix match for dynamic labels)
  3. Size threshold colors
  4. DEFAULT_LABEL_COLORS (static and dynamic)
  5. Fallback D4C5F9

This ensures user configuration takes precedence over defaults.


275-283: Critical fix: Prevents infinite recursion.

Previously, when no valid thresholds were found after parsing custom config, calling _get_custom_pr_size_thresholds() recursively would loop forever. Returning static defaults directly fixes this.

webhook_server/tests/test_labels_handler.py (4)

54-57: Fixture properly initializes new label config attributes.

Setting enabled_labels = None and label_colors = {} as defaults ensures existing tests work unchanged (all labels enabled, default colors), which is the backward-compatible behavior.


159-171: Good test for label disable behavior.

Tests that when enabled_labels = {"size"}, the hold label is not added. The test correctly:

  • Configures only size category as enabled
  • Mocks label_exists_in_pull_request to isolate the behavior
  • Verifies add_to_labels is never called

1153-1255: Comprehensive test suite for is_label_enabled.

Excellent coverage including:

  • All labels enabled when not configured (None)
  • reviewed-by labels always enabled (even with empty set)
  • Category-based enablement (branch, cherry-pick, automerge, size)
  • Unknown labels allowed by default
  • Empty enabled_labels disables configurable labels

This validates the core business rule that reviewed-by labels cannot be disabled.


1257-1305: Good coverage of custom label colors.

Tests verify:

  • Custom colors override defaults for static labels
  • Prefix-based custom colors work for dynamic labels (e.g., approved-)
  • Fallback to defaults when no custom color configured
  • Invalid color names fall back to lightgray

Comment thread webhook_server/tests/test_config_schema.py
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/hold

1 similar comment
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/hold

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/verified

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/regenerate-welcome

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/verified

@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/regenerate-welcome

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
webhook_server/utils/github_repository_and_webhook_settings.py (2)

27-39: Type annotation and loop placement are correct, but the blocking pattern may stall the event loop.

MEDIUM: The type annotation list[Future[...]] is well-formed, and moving the as_completed loop inside the with ThreadPoolExecutor() block is the correct fix (futures must be consumed before the executor context exits).

However, this entire block—including result.result() on line 38—runs synchronously and will block the async event loop. Per your coding guidelines, blocking operations in async functions should be wrapped with asyncio.to_thread().

Since this function runs at startup (not on request threads), blocking here is less critical but still inconsistent with the async design. Consider wrapping the executor block:

apis_dict = await asyncio.to_thread(_fetch_all_repository_apis, config)

Where _fetch_all_repository_apis encapsulates lines 27-39 as a synchronous helper.


44-45: Wrap synchronous functions with asyncio.to_thread() to prevent blocking the event loop.

Lines 44-45 call synchronous functions (set_all_in_progress_check_runs_to_queued and create_webhook) directly within an async function. Although these functions internally use ThreadPoolExecutor, their execution blocks the event loop on the main thread, including the blocking wait on get_future_results().

Either wrap these calls with asyncio.to_thread():

await asyncio.to_thread(set_all_in_progress_check_runs_to_queued, repo_config=config, apis_dict=apis_dict)
await asyncio.to_thread(create_webhook, config=config, apis_dict=apis_dict, secret=webhook_secret)

Or convert them to async functions that use asyncio.to_thread() or loop.run_in_executor() internally (as done with set_repositories_settings on line 43).

webhook_server/libs/handlers/runner_handler.py (1)

16-24: MAJOR: Wrap all PyGithub property accesses in asyncio.to_thread() to prevent blocking the event loop.

In the cherry_pick() async method, multiple PyGithub properties are accessed directly without wrapping. PyGithub is synchronous and blocking—accessing properties like head.ref, merge_commit_sha, title, and html_url in async code will block the event loop and degrade performance.

Line 490 accesses pull_request.head.ref directly. Additionally, lines 501–503 access merge_commit_sha, title, and html_url without wrapping. All PyGithub property accesses in async contexts must be wrapped with asyncio.to_thread(lambda: ...) to move the synchronous work to a thread pool.

Proposed fix
         async def cherry_pick(self, pull_request: PullRequest, target_branch: str, reviewed_user: str = "") -> None:
             requested_by = reviewed_user or "by target-branch label"
             self.logger.info(f"{self.log_prefix} Cherry-pick requested by user: {requested_by}")
 
-            new_branch_name = f"{CHERRY_PICKED_LABEL}-{pull_request.head.ref}-{shortuuid.uuid()[:5]}"
+            head_ref = await asyncio.to_thread(lambda: pull_request.head.ref)
+            new_branch_name = f"{CHERRY_PICKED_LABEL}-{head_ref}-{shortuuid.uuid()[:5]}"
             if not await self.is_branch_exists(branch=target_branch):
                 err_msg = f"cherry-pick failed: {target_branch} does not exists"
                 self.logger.error(err_msg)
                 await asyncio.to_thread(pull_request.create_issue_comment, err_msg)
 
             else:
                 await self.check_run_handler.set_cherry_pick_in_progress()
-                commit_hash = pull_request.merge_commit_sha
-                commit_msg_striped = pull_request.title.replace("'", "")
-                pull_request_url = pull_request.html_url
+                commit_hash = await asyncio.to_thread(lambda: pull_request.merge_commit_sha)
+                commit_msg_striped = await asyncio.to_thread(lambda: pull_request.title)
+                commit_msg_striped = commit_msg_striped.replace("'", "")
+                pull_request_url = await asyncio.to_thread(lambda: pull_request.html_url)
                 github_token = self.github_webhook.token
webhook_server/tests/test_github_api.py (1)

80-106: LOW: Consider tightening Any in test fixtures to avoid creeping dynamic typing.

Not blocking, but to_thread_sync: Any / get_value_side_effect: Any makes it easy to accidentally pass wrong shapes without mypy noticing.

Example direction:

from collections.abc import Awaitable, Callable
to_thread_sync: Callable[..., Awaitable[object]]

Also applies to: 1443-1520, 1521-1620

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

159-170: MEDIUM: Missing exception handling for add_to_labels.

The create_label and get_label operations are wrapped in try/except, but add_to_labels (line 169) is not. If the label association fails (e.g., race condition where label was deleted between creation and association), this will raise an unhandled exception.

Suggested fix
         self.logger.info(f"{self.log_prefix} Adding pull request label {label}")
-        await asyncio.to_thread(pull_request.add_to_labels, label)
-        return await self.wait_for_label(pull_request=pull_request, label=label, exists=True)
+        try:
+            await asyncio.to_thread(pull_request.add_to_labels, label)
+            return await self.wait_for_label(pull_request=pull_request, label=label, exists=True)
+        except Exception as exp:
+            self.logger.debug(f"{self.log_prefix} Failed to add {label} label. Exception: {exp}")
+            return False

332-343: LOW: Minor optimization opportunity - redundant label fetch.

pull_request_labels_names() is called twice: once at line 332 and again at line 337. Each call triggers an API request. Consider caching the result:

Suggested optimization
-        if size_label in await self.pull_request_labels_names(pull_request=pull_request):
+        current_labels = await self.pull_request_labels_names(pull_request=pull_request)
+        if size_label in current_labels:
             return
 
         exists_size_label = [
             label
-            for label in await self.pull_request_labels_names(pull_request=pull_request)
+            for label in current_labels
             if label.startswith(SIZE_LABEL_PREFIX)
         ]
🤖 Fix all issues with AI agents
In @webhook_server/libs/handlers/pull_request_handler.py:
- Around line 373-534: The welcome renderer duplicates label enablement logic
(in _is_user_label_enabled) which risks drift from LabelsHandler; replace uses
of _is_user_label_enabled in _prepare_pr_status_commands_section,
_prepare_available_labels_section, _prepare_tips_section,
_prepare_automerge_command_line and _prepare_labels_config_welcome_section with
calls to self.labels_handler.is_label_enabled(<label-or-prefix>) (or add a small
shared helper that delegates to labels_handler and use it everywhere), ensuring
you pass actual label strings/prefixes that LabelsHandler understands (e.g.,
"wip", "hold", "verified", "automerge", "cherry-pick"); also update
_prepare_no_blockers_requirement to either check
labels_handler.is_label_enabled("has-conflicts") before adding the conflict
entry or reword the line to refer to merge conflicts/state instead of a
"conflict" label so the message remains accurate.

In @webhook_server/tests/test_labels_handler.py:
- Around line 154-167: The test should stop asserting raw asyncio.to_thread call
counts and instead assert observable effects from labels_handler._add_label:
remove the `assert mock_to_thread.call_count == 3` line and replace it with
assertions like `mock_pull_request.add_to_labels.assert_called_with(mock_label)`
and assertions that the repository label modification path was exercised (e.g.,
patch the repo label's `edit()` and/or `create_label()` mocks and assert they
were called). Keep the existing patches for `label_exists_in_pull_request` and
`wait_for_label`, and use the `mock_to_thread.side_effect` only to return the
mock label and Nones as before, but validate behavior via
`mock_pull_request.add_to_labels` and the patched label `edit`/`create_label`
calls instead of checking `asyncio.to_thread` call counts.

In @webhook_server/tests/test_pull_request_handler.py:
- Around line 406-437: The tests are assigning the bound method
pull_request_handler._prepare_no_blockers_requirement instead of calling it, so
they never execute the logic; update each test (e.g.,
test_prepare_no_blockers_requirement_all_enabled,
test_prepare_no_blockers_requirement_wip_disabled,
test_prepare_no_blockers_requirement_hold_disabled,
test_prepare_no_blockers_requirement_both_disabled) to call the method by
appending parentheses (pull_request_handler._prepare_no_blockers_requirement())
and assert against the returned string as intended.

In @webhook_server/utils/constants.py:
- Around line 54-65: LABEL_CATEGORY_MAP is for exact label-string-to-category
mappings while categories like size/branch/cherry-pick are matched by prefix
elsewhere; add a short clarifying comment above LABEL_CATEGORY_MAP and
CONFIGURABLE_LABEL_CATEGORIES (referencing the symbols LABEL_CATEGORY_MAP and
CONFIGURABLE_LABEL_CATEGORIES) stating that these entries are exact-match keys
and that prefix-based labels (e.g., "size/XL", "branch/feature") are handled by
the prefix-matching logic in the codebase, so they should not be added to
LABEL_CATEGORY_MAP as full examples.
- Around line 92-95: DEFAULT_LABEL_COLORS currently aliases ALL_LABELS_DICT
which risks accidental global mutation; replace the alias with an immutable
mapping or a copy: import types and set DEFAULT_LABEL_COLORS =
types.MappingProxyType(ALL_LABELS_DICT) to prevent runtime mutation (or if you
need a mutable default, use DEFAULT_LABEL_COLORS = dict(ALL_LABELS_DICT));
update the top-level imports to include types as needed and ensure references to
DEFAULT_LABEL_COLORS remain read-only.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 295131a and cd18c05.

📒 Files selected for processing (12)
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
🧰 Additional context used
📓 Path-based instructions (4)
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Internal methods in webhook_server/libs/ can change freely - return types can change (e.g., Anybool), method signatures can be modified without deprecation

Files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_pull_request_handler.py
webhook_server/libs/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/handlers/**/*.py: Do NOT use defensive checks for architecture guarantees like repository_data which is ALWAYS set before handlers instantiate
Handler classes must implement __init__(self, github_webhook, ...) and process_event(event_data) pattern
Handlers must use self.github_webhook.unified_api for all GitHub operations, not direct PyGithub calls
Use ctx.start_step(), ctx.complete_step(), ctx.fail_step() for structured webhook execution tracking

Files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/tests/test_pull_request_handler.py
🧠 Learnings (31)
📓 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: 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.
📚 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/issue_comment_handler.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_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/issue_comment_handler.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_check_run_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/libs/handlers/runner_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-11-26T14:30:22.906Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 631
File: webhook_server_container/tests/test_github_api.py:321-327
Timestamp: 2024-11-26T14:30:22.906Z
Learning: In `webhook_server_container/tests/test_github_api.py`, when using `pytest.mark.parametrize` with indirect parameters, the `pytest.param` function may require nested lists to match the expected input structure.

Applied to files:

  • webhook_server/tests/test_github_api.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/libs/handlers/check_run_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., `user.node_id`, `user.type`, `sender`) - let KeyError surface legitimate bugs

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2024-10-14T14:13:21.316Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1632-1637
Timestamp: 2024-10-14T14:13:21.316Z
Learning: In the `ProcessGithubWehook` class in `webhook_server_container/libs/github_api.py`, avoid using environment variables to pass tokens because multiple commands with multiple tokens can run at the same time.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Store GitHub tokens in environment variables or secret management systems, never commit tokens to repository

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When accessing PyGithub properties that may trigger API calls, wrap in `asyncio.to_thread(lambda: obj.property)` instead of direct access

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Use `asyncio.gather()` for concurrent PyGithub operations to maximize performance

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • webhook_server/libs/handlers/check_run_handler.py
  • webhook_server/utils/github_repository_and_webhook_settings.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/check_run_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler classes must implement `__init__(self, github_webhook, ...)` and `process_event(event_data)` pattern

Applied to files:

  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2024-11-21T16:10:01.777Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 624
File: webhook_server_container/libs/github_api.py:2067-2101
Timestamp: 2024-11-21T16:10:01.777Z
Learning: In `webhook_server_container/libs/github_api.py`, keep the `max_owners_files` limit as a hardcoded value rather than making it configurable through the config.

Applied to files:

  • webhook_server/libs/handlers/labels_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/utils/github_repository_and_webhook_settings.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types

Applied to files:

  • webhook_server/utils/github_repository_and_webhook_settings.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the webhook server codebase, `root_approvers` and `root_reviewers` are defined as properties in the `GithubWebhook` class, not methods, so they can be accessed without parentheses and used with methods like `.copy()` directly.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Code coverage must be 90% or higher - new code without tests fails CI

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
🧬 Code graph analysis (6)
webhook_server/libs/handlers/issue_comment_handler.py (4)
webhook_server/libs/handlers/owners_files_handler.py (1)
  • is_user_valid_to_run_commands (469-501)
webhook_server/libs/handlers/pull_request_handler.py (1)
  • regenerate_welcome_message (1309-1331)
webhook_server/libs/handlers/labels_handler.py (2)
  • _remove_label (119-133)
  • _add_label (135-170)
webhook_server/libs/handlers/check_run_handler.py (2)
  • set_verify_check_queued (105-106)
  • set_verify_check_success (108-109)
webhook_server/tests/test_issue_comment_handler.py (1)
webhook_server/libs/handlers/issue_comment_handler.py (1)
  • user_commands (150-323)
webhook_server/tests/test_github_api.py (1)
webhook_server/libs/github_api.py (1)
  • _clone_repository (258-411)
webhook_server/tests/test_labels_handler.py (1)
webhook_server/libs/handlers/labels_handler.py (4)
  • get_size (299-322)
  • _add_label (135-170)
  • is_label_enabled (55-110)
  • _get_label_color (185-228)
webhook_server/utils/github_repository_and_webhook_settings.py (1)
webhook_server/tests/test_helpers.py (3)
  • result (301-302)
  • result (312-313)
  • result (323-324)
webhook_server/tests/test_pull_request_handler.py (1)
webhook_server/libs/handlers/pull_request_handler.py (2)
  • _prepare_no_blockers_requirement (484-500)
  • regenerate_welcome_message (1309-1331)
🪛 Ruff (0.14.10)
webhook_server/libs/handlers/issue_comment_handler.py

248-248: Logging statement uses f-string

(G004)

webhook_server/tests/test_github_api.py

1449-1449: Dynamically typed expressions (typing.Any) are disallowed in get_value_side_effect

(ANN401)


1450-1450: Dynamically typed expressions (typing.Any) are disallowed in to_thread_sync

(ANN401)


1485-1485: Dynamically typed expressions (typing.Any) are disallowed in **_kwargs

(ANN401)


1527-1527: Dynamically typed expressions (typing.Any) are disallowed in get_value_side_effect

(ANN401)


1528-1528: Dynamically typed expressions (typing.Any) are disallowed in to_thread_sync

(ANN401)


1571-1571: Dynamically typed expressions (typing.Any) are disallowed in **_kwargs

(ANN401)

webhook_server/libs/handlers/labels_handler.py

88-89: Logging statement uses f-string

(G004)


142-142: Logging statement uses f-string

(G004)


144-144: Logging statement uses f-string

(G004)


148-148: Logging statement uses f-string

(G004)


152-152: Logging statement uses f-string

(G004)


172-172: Boolean-typed positional argument in function definition

(FBT001)


173-173: Logging statement uses f-string

(G004)


258-259: Logging statement uses f-string

(G004)


268-269: Logging statement uses f-string

(G004)

webhook_server/libs/handlers/pull_request_handler.py

1328-1328: Logging statement uses f-string

(G004)


1330-1330: Logging statement uses f-string

(G004)

🔇 Additional comments (21)
webhook_server/utils/github_repository_and_webhook_settings.py (1)

1-1: LGTM - Proper import of Future for type annotations.

Adding Future enables the explicit type annotation on line 27.

webhook_server/tests/test_check_run_handler.py (1)

10-23: LOW: Constant rename propagation looks correct (CHERRY_PICKED_LABEL).
Tests now reference the renamed constant consistently; no behavioral risk here.

Also applies to: 379-405

webhook_server/tests/test_pull_request_handler.py (2)

84-85: LOW: Good coverage for default “all labels enabled” behavior (enabled_labels=None).
This matches the backward-compat goal.


2037-2127: LOW: Welcome regeneration tests are solid and match the “update-or-create” flow.
Covers: update existing, create new, ignore unrelated comments, select correct comment among many.

webhook_server/tests/test_issue_comment_handler.py (2)

8-22: LOW: Nice test coverage for WIP idempotence and prefix edge-cases.
These cases are easy to regress (double-prefix, “WIP:Test” w/o space).

Also applies to: 427-492


1011-1076: MEDIUM: Confirm whether /regenerate-welcome should be permission-gated (spam potential).
Tests assume any commenter can trigger it; if this must be OWNERS-only, tests (and handler) should reflect that.

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

11-25: LOW: CHERRY_PICKED_LABEL rename is applied consistently to cherry-pick check runs.

Also applies to: 203-210


283-290: LOW: Safer PyPI token masking (avoids type errors when pypi isn’t a dict).
This prevents accidental crashes during log redaction while keeping masking behavior intact.

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

243-250: OK: /regenerate-welcome command wiring is consistent with existing permission gating.

You correctly validate via OwnersFileHandler.is_user_valid_to_run_commands() and delegate to PullRequestHandler.regenerate_welcome_message().


303-316: OK: VERIFIED label side effects are now properly gated on actual label change.

Only queuing/marking the verify check when label_changed is True avoids redundant check-run updates and reduces API churn. (Matches the “minimize API calls” repo preference.)

webhook_server/tests/test_github_api.py (1)

1443-1620: OK: New tests cover the targeted fetch refspecs and fetch ordering.

These assertions lock in the intended “base branch first, then PR ref” behavior and the explicit refspec fetch for checkout_ref.

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

1033-1043: OK: Cherry-pick detection now uses the exact CHERRY_PICKED_LABEL.

The any(label.name == CHERRY_PICKED_LABEL ...) check is clearer than a prefix-based constant and matches the rename.


1309-1332: LOW: regenerate_welcome_message() is correct; consider a single-pass “find or create” to reduce API churn.

Current logic is fine, but it iterates comments and may edit() within the same pass; that’s okay inside to_thread. If you later expand the welcome marker, keep it stable to avoid updating unrelated comments.

webhook_server/tests/test_labels_handler.py (2)

59-63: OK: Tests cover the new config contract (enabled-labels + colors) well.

test_add_label_disabled_by_config plus TestIsLabelEnabled/TestCustomLabelColors exercises the critical acceptance criteria (disable categories except reviewed-by, override colors with CSS names, fallback behavior).

Also applies to: 135-149, 1147-1299


301-317: OK: Fail-fast expectation for wait_for_label exceptions is now explicitly tested.

This makes the contract unambiguous: if GitHub label propagation checks fail unexpectedly, callers see the failure instead of silently continuing.

webhook_server/libs/handlers/labels_handler.py (6)

34-42: LGTM! Clean constant definition.

Using an immutable tuple of tuples for static thresholds is appropriate. The float("inf") sentinel for the unbounded upper range is idiomatic Python.


55-110: LGTM! Well-structured enablement logic.

The method correctly implements the requirements:

  • Reviewed-by labels always enabled (critical for approval workflow)
  • Backward compatibility when enabled_labels is None
  • Category-based matching via LABEL_CATEGORY_MAP
  • Prefix matching for dynamic labels (size, branch, cherry-pick)
  • Fail-open for unknown labels avoids breaking custom workflows

The defensive isinstance check on line 86 is appropriate since enabled_labels comes from user configuration.


172-183: LGTM! Clean polling implementation.

The local TimeoutWatch instance avoids shared state issues. The 30-second timeout with 5-second intervals gives reasonable retries for label propagation.

Regarding the static analysis FBT001 hint about the boolean positional argument: this is an internal method with clear semantics (exists=True/False), so the hint can be safely ignored.


185-228: LGTM with a minor observation.

The color resolution priority is clear:

  1. Custom configured colors (direct match, then prefix)
  2. Custom size thresholds for size labels
  3. Static defaults in STATIC_LABELS_DICT / DEFAULT_LABEL_COLORS
  4. Hardcoded fallback

The "first-match-wins" comment (lines 202-203) is helpful for maintainers. Dict iteration order is guaranteed in Python 3.7+, so this is well-defined.


244-297: LGTM! Robust validation with proper fallback handling.

The method correctly:

  • Validates custom_config is a dict (line 256)
  • Validates each entry is a dict (line 266)
  • Handles YAML "inf" string → float("inf") conversion (line 276-277)
  • Validates threshold is positive numeric (line 280)
  • Returns static defaults when no valid thresholds exist (line 295)

The fix at lines 294-295 correctly avoids the infinite recursion issue by returning STATIC_PR_SIZE_THRESHOLDS directly instead of calling self._get_custom_pr_size_thresholds() again.


299-322: LGTM! Properly async with concurrent PyGithub access.

Excellent use of asyncio.gather() with asyncio.to_thread(lambda: ...) for concurrent property access (lines 301-304). This follows the coding guidelines for non-blocking PyGithub operations and maximizes performance.

Comment thread webhook_server/libs/handlers/issue_comment_handler.py
Comment thread webhook_server/libs/handlers/pull_request_handler.py
Comment thread webhook_server/tests/test_labels_handler.py
Comment thread webhook_server/tests/test_pull_request_handler.py
Comment thread webhook_server/utils/constants.py Outdated
Comment thread webhook_server/utils/constants.py
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
- Make WIP title prefix handling case-insensitive
- Remove duplicate label enablement logic in PullRequestHandler
  (now uses labels_handler.is_label_enabled() consistently)
- Fix brittle test assertions for asyncio.to_thread call counts
  in test_labels_handler.py
- Fix test method calls for _prepare_no_blockers_requirement
- Add clarifying comments for LABEL_CATEGORY_MAP explaining exact-match
  vs prefix-based label handling
- Use MappingProxyType for DEFAULT_LABEL_COLORS to prevent mutation
- Optimize push events to only clone for tags (branch pushes skip cloning
  since PushHandler only processes tags for PyPI/container builds)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
webhook_server/tests/test_github_api.py (1)

80-106: MEDIUM: tighten Any in fixtures if ANN401 is enforced for tests.
Ruff hints suggest Any may be disallowed here; if that rule is enabled, switch to Callable[..., object]/Callable[..., Awaitable[object]] and more specific return types.

Example fixture typing tweak
-from typing import Any
+from collections.abc import Awaitable, Callable
+from typing import Any

 ...
-    def to_thread_sync(self) -> Any:
+    def to_thread_sync(self) -> Callable[..., Awaitable[object]]:
         """Async helper to make asyncio.to_thread awaitable while executing inline."""
 
-        async def _to_thread_sync(fn: Any, *args: Any, **kwargs: Any) -> Any:
+        async def _to_thread_sync(fn: Callable[..., object], *args: object, **kwargs: object) -> object:
             return fn(*args, **kwargs)

Also applies to: 1443-1451

webhook_server/tests/test_labels_handler.py (1)

305-321: CRITICAL: patching an async method without AsyncMock likely breaks (or hides) real behavior.
label_exists_in_pull_request is used as an awaited call in other tests; here it’s patched without new_callable=AsyncMock. This can produce TypeError: object MagicMock can't be used in 'await' expression or (worse) silently stop testing the awaited path if implementation changes.

Fix
-                with patch.object(labels_handler, "label_exists_in_pull_request", side_effect=[False, True]):
+                with patch.object(
+                    labels_handler,
+                    "label_exists_in_pull_request",
+                    new_callable=AsyncMock,
+                    side_effect=[False, True],
+                ):
webhook_server/libs/github_api.py (2)

292-405: Wrap PyGithub property access in asyncio.to_thread() to avoid blocking the event loop.

The _clone_repository() method directly accesses self.repository.clone_url (line 294) and self.repository.owner.login (lines 314, 322) without wrapping in asyncio.to_thread(). PyGithub is synchronous and blocking; accessing its properties from an async method will freeze the event loop. Later in this same method (lines 332, 344, 368), pull_request properties are correctly wrapped with await asyncio.to_thread(lambda: ...), showing the pattern is known. Apply the same pattern to the early repository accesses:

Targeted patch
         try:
             github_token = self.token
-            clone_url_with_token = self.repository.clone_url.replace("https://", f"https://{github_token}@")
+            clone_url = await asyncio.to_thread(lambda: self.repository.clone_url)
+            clone_url_with_token = clone_url.replace("https://", f"https://{github_token}@")

             rc, _, err = await run_command(
                 command=f"git clone --depth 1 {clone_url_with_token} {self.clone_repo_dir}",
                 log_prefix=self.log_prefix,
                 redact_secrets=[github_token],
                 mask_sensitive=self.mask_sensitive,
             )

             def redact_output(value: str) -> str:
                 return _redact_secrets(value or "", [github_token], mask_sensitive=self.mask_sensitive)

             if not rc:
                 redacted_err = redact_output(err)
                 self.logger.error(f"{self.log_prefix} Failed to clone repository: {redacted_err}")
                 raise RuntimeError(f"Failed to clone repository: {redacted_err}")

             # Configure git user
             git_cmd = f"git -C {self.clone_repo_dir}"
+            owner_login = await asyncio.to_thread(lambda: self.repository.owner.login)
             rc, _, _ = await run_command(
-                command=f"{git_cmd} config user.name '{self.repository.owner.login}'",
+                command=f"{git_cmd} config user.name '{owner_login}'",
                 log_prefix=self.log_prefix,
                 mask_sensitive=self.mask_sensitive,
             )
             if not rc:
                 self.logger.warning(f"{self.log_prefix} Failed to configure git user.name")

             rc, _, _ = await run_command(
-                command=f"{git_cmd} config user.email '{self.repository.owner.login}@users.noreply.github.com'",
+                command=f"{git_cmd} config user.email '{owner_login}@users.noreply.github.com'",
                 log_prefix=self.log_prefix,
                 mask_sensitive=self.mask_sensitive,
             )

296-377: Address shallow clone limitations for three-dot diff operations.

The shallow clone with --depth 1 may lack sufficient history for git diff {base_sha}...{head_sha} (three-dot syntax) to find the merge-base reliably. While the code falls back to two-dot diff on failure, this degrades accuracy since three-dot and two-dot diffs have different semantics—three-dot shows changes since common ancestor, two-dot shows direct commit differences. This affects OWNERS file detection in PR workflows.

Consider using a full clone (remove --depth 1) or adding git fetch --unshallow after the initial clone to ensure merge-base is always available. Do not add --depth to subsequent fetch commands, as the current fetch strategy (without depth limits on specific refs) is necessary for worktree operations.

🤖 Fix all issues with AI agents
In @webhook_server/libs/github_api.py:
- Around line 329-352: The PR ref fetch currently logs only a warning which can
cause cryptic failures later; make the fetch fatal like the base branch fetch by
capturing the error from the run_command call that fetches
refs/pull/{pr_number}/head (use the same pattern as for base_ref: assign rc, _,
err = await run_command(...)), redact the err with redact_output, log an error
via self.logger.error including self.log_prefix and the redacted error, then
raise a RuntimeError describing the failed PR ref fetch (reference symbols:
pull_request, pr_number, git_cmd, run_command, redact_output, self.log_prefix,
self.logger) so callers won’t proceed assuming
refs/remotes/origin/pr/{pr_number} exists.
- Around line 682-719: The current logging in the enabled-labels block emits raw
objects from the dropped list (variable name dropped) which may include
large/untrusted YAML blobs; change the log to sanitize entries before logging by
mapping each dropped item to a small safe summary (e.g., for dicts record
"dict(keys=[...first N keys])", for lists "list(len=...)", otherwise the type
name and truncated repr up to N chars), then log that sanitized list instead of
the raw dropped objects; update the warning call referencing self.logger.warning
in the enabled-labels handling (around the enabled_set/dropped logic that sets
self.enabled_labels) to use the sanitized summaries and keep the rest of the
message and invalid-category logging with CONFIGURABLE_LABEL_CATEGORIES
unchanged.

In @webhook_server/tests/test_github_api.py:
- Around line 1443-1618: The assertions in
test_clone_repository_checkout_ref_fetch_path_for_tag and
test_clone_repository_fetches_base_branch_for_pr are brittle because they match
entire git command strings; update them to assert only on the refspecs/important
tokens to tolerate added flags. Locate the tests by name and the
executed_commands list populated by mock_run_command, then replace the strict
substring checks with contains/regex checks that look for
"refs/tags/v1.0.0:refs/tags/v1.0.0", "checkout v1.0.0" (or the tag name),
"release-1.0" for the base fetch, and
"+refs/pull/123/head:refs/remotes/origin/pr/123" for the PR fetch (or normalize
commands to extract the refspec before asserting); ensure you still verify
ordering by using indices into executed_commands for the base vs PR fetch.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd18c05 and eb4b63b.

📒 Files selected for processing (7)
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/utils/constants.py
🧰 Additional context used
📓 Path-based instructions (5)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/tests/test_github_api.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_github_api.py
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Internal methods in webhook_server/libs/ can change freely - return types can change (e.g., Anybool), method signatures can be modified without deprecation

Files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
webhook_server/libs/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/handlers/**/*.py: Do NOT use defensive checks for architecture guarantees like repository_data which is ALWAYS set before handlers instantiate
Handler classes must implement __init__(self, github_webhook, ...) and process_event(event_data) pattern
Handlers must use self.github_webhook.unified_api for all GitHub operations, not direct PyGithub calls
Use ctx.start_step(), ctx.complete_step(), ctx.fail_step() for structured webhook execution tracking

Files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
webhook_server/libs/github_api.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/github_api.py: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification
Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., user.node_id, user.type, sender) - let KeyError surface legitimate bugs
Repository cloning for check_run events should skip with early exit when action != 'completed'
Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Files:

  • webhook_server/libs/github_api.py
🧠 Learnings (27)
📓 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.
📚 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/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_github_api.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/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_github_api.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/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/utils/constants.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Code coverage must be 90% or higher - new code without tests fails CI

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/tests/test_pull_request_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Use `asyncio.gather()` for concurrent PyGithub operations to maximize performance

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.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/github_api.py
📚 Learning: 2025-02-25T12:01:42.999Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 666
File: webhook_server_container/libs/github_api.py:2254-2255
Timestamp: 2025-02-25T12:01:42.999Z
Learning: In the ProcessGithubWehook class, the `self.conventional_title` attribute is set during initialization by the `_repo_data_from_config()` method, and doesn't need additional existence checks.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When accessing PyGithub properties that may trigger API calls, wrap in `asyncio.to_thread(lambda: obj.property)` instead of direct access

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler classes must implement `__init__(self, github_webhook, ...)` and `process_event(event_data)` pattern

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/app.py : Use `create_context()` to initialize webhook context in app.py with hook_id, event_type, repository, action, sender, api_user

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Access configuration using `Config(repository='org/repo-name')` and `config.get_value('setting-name', default_value)`

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the webhook server codebase, `root_approvers` and `root_reviewers` are defined as properties in the `GithubWebhook` class, not methods, so they can be accessed without parentheses and used with methods like `.copy()` directly.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_github_api.py
🧬 Code graph analysis (5)
webhook_server/tests/test_pull_request_handler.py (2)
webhook_server/libs/handlers/pull_request_handler.py (2)
  • _prepare_no_blockers_requirement (459-475)
  • regenerate_welcome_message (1277-1299)
webhook_server/libs/handlers/labels_handler.py (1)
  • is_label_enabled (55-110)
webhook_server/libs/github_api.py (3)
webhook_server/utils/helpers.py (1)
  • run_command (266-417)
webhook_server/libs/handlers/push_handler.py (2)
  • PushHandler (18-133)
  • process_push_webhook_data (30-61)
webhook_server/libs/config.py (1)
  • get_value (132-153)
webhook_server/libs/handlers/pull_request_handler.py (1)
webhook_server/libs/handlers/labels_handler.py (1)
  • is_label_enabled (55-110)
webhook_server/tests/test_labels_handler.py (1)
webhook_server/libs/handlers/labels_handler.py (3)
  • get_size (299-322)
  • is_label_enabled (55-110)
  • _get_label_color (185-228)
webhook_server/tests/test_github_api.py (1)
webhook_server/libs/github_api.py (2)
  • _clone_repository (258-404)
  • process (406-590)
🪛 Ruff (0.14.10)
webhook_server/libs/handlers/issue_comment_handler.py

248-248: Logging statement uses f-string

(G004)

webhook_server/libs/github_api.py

340-340: Logging statement uses f-string

(G004)


341-341: Abstract raise to an inner function

(TRY301)


341-341: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Logging statement uses f-string

(G004)


364-364: Logging statement uses f-string

(G004)


444-444: Logging statement uses f-string

(G004)


447-447: Logging statement uses f-string

(G004)


450-450: Logging statement uses f-string

(G004)


701-701: Logging statement uses f-string

(G004)


707-708: Logging statement uses f-string

(G004)

webhook_server/libs/handlers/pull_request_handler.py

1296-1296: Logging statement uses f-string

(G004)


1298-1298: Logging statement uses f-string

(G004)

webhook_server/tests/test_github_api.py

1449-1449: Dynamically typed expressions (typing.Any) are disallowed in get_value_side_effect

(ANN401)


1450-1450: Dynamically typed expressions (typing.Any) are disallowed in to_thread_sync

(ANN401)


1487-1487: Dynamically typed expressions (typing.Any) are disallowed in **_kwargs

(ANN401)


1525-1525: Dynamically typed expressions (typing.Any) are disallowed in get_value_side_effect

(ANN401)


1526-1526: Dynamically typed expressions (typing.Any) are disallowed in to_thread_sync

(ANN401)


1569-1569: Dynamically typed expressions (typing.Any) are disallowed in **_kwargs

(ANN401)

🔇 Additional comments (24)
webhook_server/tests/test_github_api.py (1)

47-52: LOW: Good alignment with new “tag pushes only” cloning behavior.
The updated push payload and test_process_push_event now exercise the tag-only path, matching the new process() branching.

Also applies to: 312-348

webhook_server/tests/test_labels_handler.py (2)

82-106: LOW: Async conversion for size tests is solid and matches the handler’s asyncio.gather() pattern.
Coverage around thresholds/boundaries is useful and not overly brittle.

Also applies to: 451-475


1183-1246: No action required—exact "lgtm" / "approve" labels are never created or used.

The code only creates prefixed reviewed-by labels (e.g., lgtm-username, approved-username), not the exact static labels "lgtm" or "approve". These prefixed labels are already tested and are always enabled via the reviewed_by_prefixes check. The exact labels in LABEL_CATEGORY_MAP appear to be unused; the misleading "Always enabled" comments should be clarified in the constants file, but there is no functional impact on merge/LGTM flows.

Likely an incorrect or invalid review comment.

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

24-24: LGTM - Import aligns with constant rename.

The import of CHERRY_PICKED_LABEL replaces the previous prefix-based constant, consistent with the codebase-wide refactoring for configurable labels.


257-262: LGTM - Welcome comment template integration.

The new section properties are correctly integrated into the welcome message template, maintaining proper formatting with backslash line continuations.


267-307: LGTM - Modular welcome section integrations.

All new section properties are correctly wired into the welcome comment template. The modular approach improves maintainability and enables dynamic content based on label configuration.


372-384: LGTM - Labels configuration section logic.

The three-way conditional correctly distinguishes between:

  1. None → default (all enabled)
  2. Empty set → all configurable labels disabled
  3. Non-empty set → specific categories enabled

Using sorted() ensures deterministic output.


386-413: LGTM - Conditional PR status commands.

Commands are correctly gated on label enablement via is_label_enabled(). The /reprocess and /regenerate-welcome commands are correctly always available since they don't depend on configurable labels.


415-428: LGTM - Available labels section.

Correctly filters USER_LABELS_DICT using is_label_enabled() and handles the empty case with an informative message.


430-456: LGTM - Conditional tips section.

Tips are correctly gated on label enablement. The container builds tip correctly checks build_and_push_container config (not a label), and permission tips are always included as they're universally applicable.


458-475: LGTM - No blockers requirement line.

The conflict blocker is correctly always included as it's fundamental to merge eligibility. The comment at line 472-473 clearly documents this design decision.


477-501: LGTM - Automerge and cherry-pick sections.

Both properties correctly gate their output on label enablement:

  • Automerge uses AUTOMERGE_LABEL_STR
  • Cherry-pick uses CHERRY_PICKED_LABEL which maps to the "cherry-pick" category in is_label_enabled()

1002-1002: LGTM - Cherry-picked label exact match.

The change from prefix-based matching to exact label comparison using CHERRY_PICKED_LABEL constant is consistent with the codebase-wide refactoring.


1277-1299: LGTM - Welcome message regeneration.

Implementation correctly:

  1. Wraps PyGithub calls in asyncio.to_thread() per coding guidelines
  2. Encapsulates iteration + edit in a single to_thread call to avoid N+1 thread spawning
  3. Creates new comment if none exists (fallback behavior)

Regarding static analysis G004 (f-string logging) at lines 1296/1298: Per project coding guidelines, f-strings are the established style for logging in this codebase.

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

27-27: LGTM - Regenerate-welcome command registration.

The COMMAND_REGENERATE_WELCOME_STR import and its addition to available_commands correctly enables the new /regenerate-welcome slash command.

Also applies to: 162-162


243-249: LGTM - Regenerate-welcome command handler.

The handler correctly:

  1. Validates user permissions before execution
  2. Delegates to pull_request_handler.regenerate_welcome_message()
  3. Follows the same pattern as /reprocess command

f-string logging at line 248 follows project conventions.


266-286: LGTM - Idempotent WIP handling.

The changes correctly implement idempotency:

  • Title edits only occur when label state actually changes (label_changed)
  • Case-insensitive prefix detection (upper()) prevents edge cases
  • Both "WIP: " (with space) and "WIP:" (without space) variants are handled

This prevents duplicate prefixes and unnecessary API calls.


303-315: LGTM - Idempotent verified label handling.

Check run updates are now gated on actual label state changes, preventing unnecessary GitHub API calls when the label is already in the desired state.


433-433: LGTM - Callable type annotation.

Callable[..., Any] is appropriate here because the mapped functions (run_tox, run_pre_commit, run_build_container, etc.) have varying signatures. The ellipsis (...) accommodates this heterogeneity.

webhook_server/tests/test_pull_request_handler.py (5)

17-17: LGTM - Test imports updated.

Imports correctly updated for:

  • CHERRY_PICKED_LABEL (renamed constant)
  • HOLD_LABEL_STR (needed for no-blockers tests)

Also applies to: 20-20


85-85: LGTM - Fixture default for enabled_labels.

Setting enabled_labels = None correctly represents the default configuration where all labels are enabled, matching the implementation behavior.


407-444: LGTM - Comprehensive no-blockers tests.

Tests cover all four combinations of WIP/hold enablement:

  1. Both enabled → "WIP, hold, conflict"
  2. WIP disabled → "hold, conflict"
  3. Hold disabled → "WIP, conflict"
  4. Both disabled → "conflict" only

The mocking strategy using side_effect=lambda label: is clean and readable.


694-694: LGTM - Cherry-picked label constant in tests.

Tests correctly updated to use CHERRY_PICKED_LABEL constant, consistent with the codebase-wide rename.

Also applies to: 715-715


2044-2135: LGTM - Comprehensive regenerate_welcome_message tests.

Excellent test coverage including:

  1. Existing comment updated - verifies edit() called, not create_issue_comment()
  2. No existing comment - verifies new comment created
  3. Other comments ignored - verifies non-matching comments not edited
  4. Correct comment among many - verifies only the welcome comment is edited

Each test properly isolates the behavior being tested and includes logging verification.

Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/utils/constants.py
Comment thread webhook_server/utils/constants.py Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
- Make PR ref fetch fatal instead of warning (fail-fast)
- Sanitize enabled-labels logging to avoid large YAML blobs
- Make git command test assertions less brittle (refspec matching)
- Protect lgtm/approve labels from being silently disabled
- Make ALL_LABELS_DICT immutable with MappingProxyType
- Fix AsyncMock patching in test_labels_handler.py
- Tighten Any types to Callable/Awaitable in test fixtures
- Wrap PyGithub property access in asyncio.to_thread
- Fix Headers type issues in tests
- Remove assert statements from production code (use type: ignore)
- Remove defensive isinstance check per CLAUDE.md
- Add SafeRotatingFileHandler to fix log rotation crash
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
webhook_server/tests/test_github_api.py (2)

873-908: HIGH: Remove unused minimal_hook_data fixture parameter.

This test defines its own commit_data dict locally, so the minimal_hook_data fixture parameter is unused. Same rationale as above—remove to avoid confusion.

Proposed fix
     @pytest.mark.asyncio
     async def test_get_pull_request_by_commit_with_pulls(
-        self, minimal_hook_data: dict, minimal_headers: Headers, logger: Mock
+        self, minimal_headers: Headers, logger: Mock
     ) -> None:
         """Test getting pull request by commit with pulls."""
         commit_data = {

1834-1900: MEDIUM: Remove unused logger fixture parameters in cleanup tests.

Both test_cleanup and test_cleanup_exception declare logger: Mock but create their own mock_logger = Mock() inside the test. While this works, it's inconsistent and creates confusion.

WHY this matters: Either use the fixture or define locally—not both. Consistency helps maintainability.

Proposed fix for test_cleanup (line 1835-1840)
     @pytest.mark.asyncio
     async def test_cleanup(
         self,
         minimal_hook_data: dict,
         minimal_headers: Headers,
-        logger: Mock,
         to_thread_sync: Callable[..., Awaitable[object]],
     ) -> None:
Proposed fix for test_cleanup_exception (line 1868-1873)
     @pytest.mark.asyncio
     async def test_cleanup_exception(
         self,
         minimal_hook_data: dict,
         minimal_headers: Headers,
-        logger: Mock,
         to_thread_sync: Callable[..., Awaitable[object]],
     ) -> None:
🤖 Fix all issues with AI agents
In @webhook_server/tests/test_github_api.py:
- Around line 738-748: The test function test_process_check_run_event declares
unused fixture parameters minimal_hook_data and minimal_headers; remove these
parameters from the test signature so pytest won’t run unnecessary fixture setup
and to make intent clear, i.e., update the def of test_process_check_run_event
to only accept the used logger parameter (or any other actually used fixtures)
and leave the locally-defined check_run_data and headers as-is.

In @webhook_server/tests/test_safe_rotating_handler.py:
- Line 12: Remove the unnecessary "noqa: F401" on the import line "from
webhook_server.utils import helpers" so the import remains but the noqa
directive is dropped; the import is actually used for side effects and later
referenced in the patch verification assertion around line ~190, so simply
delete the " # noqa: F401 - import triggers patching" suffix and run lint/tests
to ensure no warnings remain.

In @webhook_server/utils/safe_rotating_handler.py:
- Line 20: The noqa directive on the method definition doRollover is
unnecessary; remove the trailing "# noqa: N802" from the def doRollover(self) ->
None: line in safe_rotating_handler.py (and search for any other unused N802
directives in this file) so the function definition is clean—no other code
changes needed as the method name is intentionally camelCase and your linter
config does not require N802 suppression.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb4b63b and 852bb0b.

📒 Files selected for processing (8)
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/utils/helpers.py
  • webhook_server/utils/safe_rotating_handler.py
🧰 Additional context used
📓 Path-based instructions (5)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/helpers.py
  • webhook_server/tests/test_labels_handler.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Internal methods in webhook_server/libs/ can change freely - return types can change (e.g., Anybool), method signatures can be modified without deprecation

Files:

  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
webhook_server/libs/github_api.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/github_api.py: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification
Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., user.node_id, user.type, sender) - let KeyError surface legitimate bugs
Repository cloning for check_run events should skip with early exit when action != 'completed'
Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Files:

  • webhook_server/libs/github_api.py
webhook_server/libs/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/handlers/**/*.py: Do NOT use defensive checks for architecture guarantees like repository_data which is ALWAYS set before handlers instantiate
Handler classes must implement __init__(self, github_webhook, ...) and process_event(event_data) pattern
Handlers must use self.github_webhook.unified_api for all GitHub operations, not direct PyGithub calls
Use ctx.start_step(), ctx.complete_step(), ctx.fail_step() for structured webhook execution tracking

Files:

  • webhook_server/libs/handlers/labels_handler.py
🧠 Learnings (29)
📓 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.
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/constants.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/utils/helpers.py
  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_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/utils/constants.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_labels_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/utils/constants.py
  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_labels_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/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2025-02-25T12:01:42.999Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 666
File: webhook_server_container/libs/github_api.py:2254-2255
Timestamp: 2025-02-25T12:01:42.999Z
Learning: In the ProcessGithubWehook class, the `self.conventional_title` attribute is set during initialization by the `_repo_data_from_config()` method, and doesn't need additional existence checks.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When accessing PyGithub properties that may trigger API calls, wrap in `asyncio.to_thread(lambda: obj.property)` instead of direct access

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler classes must implement `__init__(self, github_webhook, ...)` and `process_event(event_data)` pattern

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/app.py : Use `create_context()` to initialize webhook context in app.py with hook_id, event_type, repository, action, sender, api_user

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Access configuration using `Config(repository='org/repo-name')` and `config.get_value('setting-name', default_value)`

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/utils/helpers.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_labels_handler.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2024-11-21T16:10:01.777Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 624
File: webhook_server_container/libs/github_api.py:2067-2101
Timestamp: 2024-11-21T16:10:01.777Z
Learning: In `webhook_server_container/libs/github_api.py`, keep the `max_owners_files` limit as a hardcoded value rather than making it configurable through the config.

Applied to files:

  • webhook_server/libs/handlers/labels_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely - return types can change (e.g., `Any` → `bool`), method signatures can be modified without deprecation

Applied to files:

  • webhook_server/utils/helpers.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types

Applied to files:

  • webhook_server/utils/helpers.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Use `get_logger_with_params()` for logging with structured context (name, repository, hook_id)

Applied to files:

  • webhook_server/utils/helpers.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/utils/context.py : Webhook logs should be written to `{config.data_dir}/logs/webhooks_YYYY-MM-DD.json` in pretty-printed JSON format (2-space indentation) with daily rotation based on UTC date

Applied to files:

  • webhook_server/utils/helpers.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Do NOT use hasattr() checks for required parameters in `__init__()` - if config is required, access it directly without defensive checks

Applied to files:

  • webhook_server/utils/helpers.py
🧬 Code graph analysis (6)
webhook_server/utils/safe_rotating_handler.py (1)
webhook_server/libs/config.py (1)
  • exists (27-29)
webhook_server/tests/test_safe_rotating_handler.py (1)
webhook_server/utils/safe_rotating_handler.py (2)
  • SafeRotatingFileHandler (9-65)
  • doRollover (20-65)
webhook_server/libs/github_api.py (1)
webhook_server/libs/config.py (1)
  • get_value (132-153)
webhook_server/libs/handlers/labels_handler.py (1)
webhook_server/tests/conftest.py (2)
  • pull_request (106-107)
  • PullRequest (90-102)
webhook_server/utils/helpers.py (1)
webhook_server/utils/safe_rotating_handler.py (1)
  • SafeRotatingFileHandler (9-65)
webhook_server/tests/test_labels_handler.py (1)
webhook_server/libs/handlers/labels_handler.py (4)
  • get_size (297-320)
  • _add_label (133-168)
  • is_label_enabled (55-108)
  • _get_label_color (183-226)
🪛 Ruff (0.14.10)
webhook_server/utils/safe_rotating_handler.py

20-20: Unused noqa directive (non-enabled: N802)

Remove unused noqa directive

(RUF100)

webhook_server/tests/test_safe_rotating_handler.py

12-12: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


95-95: Avoid specifying long messages outside the exception class

(TRY003)


134-134: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/libs/github_api.py

321-321: Logging statement uses f-string

(G004)


329-329: Logging statement uses f-string

(G004)


342-342: Logging statement uses f-string

(G004)


343-343: Abstract raise to an inner function

(TRY301)


343-343: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Logging statement uses f-string

(G004)


355-355: Abstract raise to an inner function

(TRY301)


355-355: Avoid specifying long messages outside the exception class

(TRY003)


367-367: Logging statement uses f-string

(G004)


446-446: Logging statement uses f-string

(G004)


449-449: Logging statement uses f-string

(G004)


452-452: Logging statement uses f-string

(G004)


705-705: Dynamically typed expressions (typing.Any) are disallowed in item

(ANN401)


721-721: Logging statement uses f-string

(G004)


728-729: Logging statement uses f-string

(G004)

webhook_server/tests/test_github_api.py

740-740: Unused method argument: minimal_hook_data

(ARG002)


740-740: Unused method argument: minimal_headers

(ARG002)


875-875: Unused method argument: minimal_hook_data

(ARG002)


1394-1394: Unnecessary list comprehension (rewrite using list())

Rewrite using list()

(C416)


1839-1839: Unused method argument: logger

(ARG002)


1872-1872: Unused method argument: logger

(ARG002)

webhook_server/libs/handlers/labels_handler.py

140-140: Logging statement uses f-string

(G004)


142-142: Logging statement uses f-string

(G004)


146-146: Logging statement uses f-string

(G004)


150-150: Logging statement uses f-string

(G004)


170-170: Boolean-typed positional argument in function definition

(FBT001)


171-171: Logging statement uses f-string

(G004)


256-257: Logging statement uses f-string

(G004)


266-267: Logging statement uses f-string

(G004)

🔇 Additional comments (38)
webhook_server/utils/helpers.py (2)

20-34: LGTM - Module-level patching for log rotation safety.

The monkey-patch approach at import time is appropriate here. The comment clearly explains WHY this exists: preventing crashes when backup log files are missing during rollover. This is a reasonable solution since simple_logger doesn't provide a hook for custom handlers.

One consideration: this patch affects all uses of simple_logger in the process, which is the intended behavior for consistent crash resilience.


520-520: LGTM - Improved type annotation precision.

Changing list[Future] to list[Future[Any]] is more explicit and aligns with mypy strict mode requirements. The Any type parameter correctly reflects that futures can return various types.

webhook_server/utils/safe_rotating_handler.py (2)

9-18: LGTM - Clear documentation of failure mode and solution.

The docstring properly explains WHY this class exists: the standard RotatingFileHandler crashes when backup files are deleted externally during rollover. This is a real-world scenario with logrotate or manual cleanup.


26-65: LGTM - Robust rollover with proper TOCTOU handling.

The implementation correctly acknowledges and handles the race condition between os.path.exists() check and subsequent file operations. Catching both FileNotFoundError and OSError at each operation point ensures the handler remains functional even under adversarial filesystem conditions.

The final self.stream = self._open() on line 65 ensures logging can continue after rollover regardless of what happened to backup files.

webhook_server/tests/test_safe_rotating_handler.py (3)

16-44: LGTM - Solid basic rollover test.

The test properly exercises the rollover mechanism by emitting enough records to trigger multiple rollovers. Using try/finally ensures cleanup happens even on test failure.


69-107: LGTM - Good TOCTOU race condition simulation.

This test correctly simulates the race where os.path.exists() returns True but the file disappears before os.remove() can act. The mocking strategy is appropriate for testing this failure mode.


179-190: LGTM - Essential integration test for the patching mechanism.

This test ensures the monkey-patch applied in helpers.py remains effective. If someone accidentally removes or reorders imports, this test will catch the regression.

webhook_server/tests/test_labels_handler.py (6)

12-21: LGTM - Imports align with new constants.

The imports correctly reference the new public constants introduced in constants.py: AUTOMERGE_LABEL_STR, BRANCH_LABEL_PREFIX, CHERRY_PICK_LABEL_PREFIX, CHERRY_PICKED_LABEL, and VERIFIED_LABEL_STR.


25-29: LGTM - Cleaner type annotation.

Changing from int | None = 50 to int = 50 is correct since there's no actual None handling in the class, and the default values are always integers.


59-62: LGTM - Essential mock attributes for label configuration testing.

Adding enabled_labels = None and label_colors = {} to the mock webhook correctly reflects the default state: all labels enabled, no custom colors.


135-148: LGTM - Critical test for label disablement.

This test verifies the core feature: when enabled_labels restricts to only {"size"}, other labels like "hold" should not be added. The assertion result is False and mock_pull_request.add_to_labels.assert_not_called() correctly validate the behavior.


1158-1278: LGTM - Comprehensive label enablement tests.

The TestIsLabelEnabled class thoroughly covers:

  • All labels enabled when enabled_labels=None (backward compatibility)
  • reviewed-by labels always enabled (cannot be disabled - critical for approval workflow)
  • Category-specific enablement (size, branch, cherry-pick)
  • Unknown labels allowed by default
  • Empty set disables configurable labels but keeps reviewed-by enabled
  • LGTM and APPROVE always enabled (required for review workflow)

This test coverage ensures the PR objectives are met.


1280-1328: LGTM - Custom color configuration tests.

Tests correctly verify:

  • Custom colors override defaults for static labels
  • Dynamic prefix colors work (e.g., "approved-": "purple")
  • Fallback to default when no custom color
  • Invalid color names fallback to default
webhook_server/libs/github_api.py (6)

294-298: LGTM - Shallow clone optimization.

Using --depth 1 significantly reduces clone time and bandwidth. Since the subsequent fetches get only needed refs, this is a sound optimization.


331-367: LGTM - Selective fetch strategy.

Fetching only required refs is a major improvement:

  • For PRs: fetch base branch + specific PR ref
  • For tags: fetch only the specific tag

This avoids the previous approach of fetching all refs, which was wasteful for large repositories with many branches.


356-367: MEDIUM: Consider adding error handling for tag fetch failures.

For PR refs, fetch failures raise RuntimeError (lines 343, 355). For tag fetches, failures only log a warning (line 367). This inconsistency could lead to silent failures in tag processing.

Is this intentional? If tag processing should fail gracefully, the current behavior is fine. If tag fetch failures should abort processing, consider raising an error.


437-453: LGTM - Smart skip for branch pushes.

Since PushHandler only processes tags (PyPI upload, container build), skipping the clone for branch pushes is correct. This avoids unnecessary I/O for branch push events that would otherwise clone and do nothing.


684-739: LGTM - Robust labels configuration loading with validation.

Key implementation highlights:

  1. Backward compatibility: enabled_labels = None when not configured means all labels remain enabled
  2. Safe merging: Global config is overridden by repo-specific config
  3. Validation: Invalid categories are logged and excluded, non-string entries are sanitized before logging
  4. Security: The _sanitize_item helper prevents log injection from malicious YAML blobs

The intersection with CONFIGURABLE_LABEL_CATEGORIES on line 731 ensures only valid categories are stored.


619-630: LGTM - Type coercion for config values.

The defensive coercion pattern (_pypi if isinstance(_pypi, dict) else {}) ensures type safety when config values might be misconfigured as non-dict types. This follows the coding guidelines that allow defensive checks for config values that could be malformed.

webhook_server/utils/constants.py (4)

1-2: LGTM - Imports for immutability.

Using types.MappingProxyType to create read-only views of dictionaries prevents accidental mutation of shared label data structures.


57-80: LGTM - Excellent documentation distinguishing exact-match from prefix-based labels.

The extensive comment block (lines 57-69) clearly explains:

  • This map is for EXACT-MATCH labels only
  • PREFIX-BASED labels are handled separately in is_label_enabled()
  • Why certain labels should NOT be added here

This documentation prevents future maintainers from adding prefix-based labels to this map.


105-111: LGTM - Immutable default colors via MappingProxyType.

Using MappingProxyType for ALL_LABELS_DICT and DEFAULT_LABEL_COLORS prevents accidental mutations that could cause subtle bugs when multiple handlers share these constants.


113-126: LGTM - Configurable categories correctly excludes reviewed-by.

The comment on line 114 explicitly documents WHY reviewed-by is excluded: "it cannot be disabled". This aligns with the PR objective that reviewed-by label families drive approval/LGTM/changes-requested/auto-merge logic.

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

15-29: LGTM - Imports align with constants refactoring.

The new imports (BRANCH_LABEL_PREFIX, CHERRY_PICK_LABEL_PREFIX, CHERRY_PICKED_LABEL, DEFAULT_LABEL_COLORS, LABEL_CATEGORY_MAP) support the configurable labels feature.


34-42: LGTM - Static defaults as module-level constant.

Defining STATIC_PR_SIZE_THRESHOLDS as an immutable tuple at module level:

  1. Provides clear defaults when no custom config exists
  2. Prevents recursion issues (the old code could call _get_custom_pr_size_thresholds recursively)
  3. Serves as documentation of expected size categories

55-108: LGTM - Correct priority order in label enablement logic.

The implementation follows the correct precedence:

  1. Always enabled: reviewed-by prefixes (lines 72-79), exact LGTM/APPROVE (lines 82-83)
  2. All enabled if not configured: enabled_labels is None (lines 88-89)
  3. Static labels: via LABEL_CATEGORY_MAP (lines 92-93)
  4. Prefix-based: size, branch, cherry-pick categories (lines 96-105)
  5. Unknown allowed: default True (lines 107-108)

This ensures reviewed-by labels CANNOT be disabled even if someone tries to configure an empty set.


133-168: LGTM - _add_label now respects configuration and returns result.

Key changes:

  • Returns False early when label is disabled (lines 145-147)
  • Returns the result of wait_for_label (line 168) for proper success indication
  • Enables callers to know if the label was actually added

183-226: LGTM - Comprehensive color resolution with proper fallback chain.

The priority order is correct:

  1. Custom configured colors (direct match, line 196-197)
  2. Custom prefix matches (lines 202-204)
  3. Custom size thresholds (lines 207-215)
  4. DEFAULT_LABEL_COLORS for static labels (lines 218-219)
  5. DEFAULT_LABEL_COLORS for dynamic prefixes (lines 222-224)
  6. Hardcoded fallback "D4C5F9" (line 226)

This ensures custom colors always take precedence while maintaining sensible defaults.


242-295: LGTM - Robust threshold parsing with recursion fix.

The recursion issue mentioned in PR objectives is fixed:

  • Lines 291-293 return list(STATIC_PR_SIZE_THRESHOLDS) directly instead of calling self recursively
  • Type validation (lines 254-259, 264-269) prevents crashes from misconfigured YAML

297-320: LGTM - Async get_size correctly wraps PyGithub properties.

Using asyncio.gather with asyncio.to_thread(lambda: pull_request.additions/deletions) is the correct pattern per coding guidelines: "PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread()".

webhook_server/tests/test_github_api.py (8)

1-17: LGTM: Import updates are appropriate.

The addition of Awaitable and Callable from collections.abc aligns with the updated fixture type annotations throughout the file. Using collections.abc over typing is the modern approach for Python 3.9+.


72-74: LGTM: Fixture type annotation improvement.

Using Headers as the return type instead of a plain dict improves type safety and ensures test fixtures match the actual types used in production code.


80-106: LGTM: Well-typed async helper fixtures.

The to_thread_sync and get_value_side_effect fixtures now have proper Callable[..., Awaitable[object]] and Callable[..., object] return types. This matches the coding guidelines requirement for complete type hints.


1508-1583: LGTM: Solid test for tag checkout path.

This test validates the tag-based cloning semantics with proper assertions:

  1. Verifies the tag refspec format refs/tags/v1.0.0:refs/tags/v1.0.0
  2. Verifies checkout command targets the tag name
  3. Uses flexible matching to tolerate git flags variations

The command tracking approach with executed_commands list is a clean pattern for verifying command sequences.


1585-1684: LGTM: Well-designed test for base branch fetch ordering.

This test validates an important invariant: base branch fetch must occur BEFORE PR ref fetch. The ordering assertion using list indices is an effective approach.

The test covers:

  1. Base branch release-1.0 is fetched first
  2. PR refspec +refs/pull/123/head:refs/remotes/origin/pr/123 is fetched second
  3. Order verification via index comparison

1772-1832: LGTM: Clear test for branch push skip behavior.

The docstring update and test logic correctly reflect that branch pushes skip cloning entirely because PushHandler only processes tags. Key assertions:

  • _clone_repository is NOT called
  • PushHandler.process_push_webhook_data is NOT called
  • Returns None

This aligns with the AI summary stating "branch pushes skip cloning and the PushHandler processing."


1902-1958: LGTM: Excellent test for label configuration sanitization.

This test validates defensive handling of non-string entries in enabled-labels:

  • Dict entries are sanitized and logged as dict(keys=...)
  • List entries are sanitized and logged as list(len=N)
  • Integer entries are sanitized and logged as int(...)
  • Only valid string entries (verified) are kept

The assertions verify both the warning message format and the resulting enabled_labels state. This aligns with the PR objective to validate enabled-labels configuration.


46-53: LGTM: Push payload fixture updated for tag-based cloning.

The docstring and ref value now correctly reflect that only tag pushes trigger cloning. Using refs/tags/v1.0.0 is appropriate for the updated cloning semantics.

Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_safe_rotating_handler.py Outdated
Comment thread webhook_server/utils/safe_rotating_handler.py Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
…ectives

- Remove unused fixture parameters from test_process_check_run_event
- Simplify list comprehension in test_clone_repository_pr_ref_fetch_failure_raises
- Remove unused noqa directives from safe_rotating_handler.py and test file
- Remove unused logger fixture params in cleanup tests
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/build-and-push-container

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webhook_server/tests/test_github_api.py (1)

1200-1205: Normalize mock signatures to match run_command—make all mocks consistent.

Production code always calls run_command(command=..., log_prefix=..., ...) using keyword arguments, so all three mocks work correctly today. However, mocks at lines 1252–1258 and 1307–1313 deviate from the real function signature, which creates confusion for future maintainers.

Recommendation: align lines 1252 and 1307 with line 1200's pattern. Change async def mock_run_command(**kwargs: object) to async def mock_run_command(command: str, log_prefix: str, **kwargs: object), then extract command directly instead of via kwargs.get("command", "").

Example fix pattern
-                            async def mock_run_command(**kwargs: object) -> tuple[bool, str, str]:
-                                command = kwargs.get("command", "")
+                            async def mock_run_command(command: str, log_prefix: str, **kwargs: object) -> tuple[bool, str, str]:
                                 if "checkout main" in command:
                                     return (False, "", "Branch not found")
                                 return (True, "", "")
🤖 Fix all issues with AI agents
In @webhook_server/tests/test_github_api.py:
- Around line 872-876: The test function
test_get_pull_request_by_commit_with_pulls declares fixture parameters
minimal_hook_data, minimal_headers, and logger but one or more are unused and
trigger ruff ARG002; rename any unused fixtures by prefixing them with an
underscore (e.g., _minimal_headers or _logger) in the test signature so the
fixtures remain available but signal intentional non-use while keeping the
function name and other parameters unchanged.
- Around line 1904-1955: The test
test_enabled_labels_with_non_string_entries_logs_warning is brittle because it
assumes the non-string warning is the first mock_logger.warning call; change the
assertion to search through mock_logger.warning.call_args_list for any call
whose message contains "Non-string entries in enabled-labels were ignored" and
the expected substrings ("dict(keys=", "list(len=2)", "int("), instead of
indexing the first call — update the checks that build non_string_warning from
mock_logger.warning.call_args_list and the subsequent asserts to use any(...) or
a loop to find and assert on the matching warning message.

In @webhook_server/tests/test_safe_rotating_handler.py:
- Around line 95-99: The mock_remove function currently raises FileNotFoundError
with a custom message; to satisfy ruff TRY003, change it to raise
FileNotFoundError() (no long custom message) in mock_remove and make the same
change in the second mock_remove occurrence (the other test block around the
later mock) so both raise the exception instance without a custom string.
- Around line 12-16: Replace the current "import for side effect" pattern that
uses a fake usage assignment: change the import line to an explicit alias import
(e.g., from webhook_server.utils import helpers as _helpers or as _), remove the
redundant "_ = helpers" line, and keep the comment noting the import is for side
effects; update references to the alias if needed to avoid unused-import
warnings.
- Around line 150-176: The test test_rollover_with_nonexistent_base_file calls
os.remove(log_file) while handler.stream may be open, which fails on Windows;
change the test to avoid deleting an open file by simulating the deletion
instead: either (A) if you prefer a simple OS guard, skip or do not call
os.remove on Windows (check os.name == "nt") and emulate the missing base file
by monkeypatching os.path.exists/log file checks before calling
handler.doRollover, or (B) better, patch os.remove (or os.path.exists) using
unittest.mock.patch or pytest's monkeypatch so that when handler.doRollover runs
it sees the file as absent without actually removing the open file; update the
test_rollover_with_nonexistent_base_file to use this approach and ensure
handler.doRollover still executes and handler.stream is non-None afterwards.

In @webhook_server/utils/safe_rotating_handler.py:
- Around line 10-25: The docstring for doRollover currently claims it "logs a
warning instead of crashing" but the implementation silently suppresses
FileNotFoundError; update the docstring of SafeRotatingHandler.doRollover to
remove the promise of internal logging and instead state that FileNotFoundError
is caught and suppressed to allow logging to continue without raising, and note
that callers should perform external warnings if they need explicit notification
(avoid adding logging inside doRollover to prevent recursion). Ensure the method
name doRollover and the class/module safe_rotating_handler are referenced so the
change is made in the correct place.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 852bb0b and fb242c3.

📒 Files selected for processing (3)
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/safe_rotating_handler.py
🧰 Additional context used
📓 Path-based instructions (2)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
🧠 Learnings (18)
📓 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.
📚 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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Do NOT use hasattr() checks for library versions we control in `pyproject.toml` (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely - return types can change (e.g., `Any` → `bool`), method signatures can be modified without deprecation

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When accessing PyGithub properties that may trigger API calls, wrap in `asyncio.to_thread(lambda: obj.property)` instead of direct access

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/tests/test_github_api.py
🧬 Code graph analysis (3)
webhook_server/utils/safe_rotating_handler.py (1)
webhook_server/libs/config.py (1)
  • exists (27-29)
webhook_server/tests/test_safe_rotating_handler.py (1)
webhook_server/utils/safe_rotating_handler.py (2)
  • SafeRotatingFileHandler (9-65)
  • doRollover (20-65)
webhook_server/tests/test_github_api.py (2)
webhook_server/tests/test_helpers.py (3)
  • get_value_side_effect (176-183)
  • get_value_side_effect (199-206)
  • get_value_side_effect (222-229)
webhook_server/tests/conftest.py (1)
  • base (95-96)
🪛 Ruff (0.14.11)
webhook_server/tests/test_safe_rotating_handler.py

97-97: Avoid specifying long messages outside the exception class

(TRY003)


136-136: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/tests/test_github_api.py

874-874: Unused method argument: minimal_hook_data

(ARG002)

🔇 Additional comments (1)
webhook_server/tests/test_github_api.py (1)

23-29: LOW: Headers migration looks good.

Why: tests now match the real request header type (Headers), reducing “dict vs Headers” drift.

Also applies to: 72-75, 229-231

Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_safe_rotating_handler.py Outdated
Comment thread webhook_server/tests/test_safe_rotating_handler.py
Comment thread webhook_server/tests/test_safe_rotating_handler.py
Comment thread webhook_server/utils/safe_rotating_handler.py
Comment thread webhook_server/utils/safe_rotating_handler.py
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/build-and-push-container

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

- Fix ruff ARG002 unused fixture parameter
- Fix order-dependent warning assertion
- Fix unused import workaround with alias pattern
- Fix ruff TRY003 by removing custom exception messages
- Fix Windows portability issue with os.path.exists patch
- Update doRollover docstring to reflect actual behavior
- Add comprehensive OSError suppression documentation
- Normalize mock signatures to match run_command
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webhook_server/tests/test_github_api.py (1)

872-907: LOW: Remove unused minimal_hook_data fixture parameter.

The test declares minimal_hook_data but creates and uses commit_data instead. WHY this matters: unused parameters in test signatures create confusion about test dependencies and can lead to maintenance overhead when fixtures change.

🔧 Suggested fix
 @pytest.mark.asyncio
 async def test_get_pull_request_by_commit_with_pulls(
-    self, minimal_hook_data: dict, minimal_headers: Headers, logger: Mock
+    self, minimal_headers: Headers, logger: Mock
 ) -> None:
🤖 Fix all issues with AI agents
In @webhook_server/tests/test_github_api.py:
- Around line 1376-1380: The mock_run_command uses **kwargs so positional calls
to run_command (e.g., run_command(command_str, log_prefix)) won't be captured;
change the mock signature to match other tests by declaring async def
mock_run_command(command: str, log_prefix: str) -> tuple[bool, str, str]: and
then inspect the command variable (if "fetch origin +refs/pull/" in command:
return (False, "", "Fetch failed: PR ref not found") else return (True, "", ""))
so positional calls are handled correctly.

In @webhook_server/tests/test_safe_rotating_handler.py:
- Line 12: Remove the now-unnecessary noqa directive from the side-effect import
of webhook_server.utils.helpers: change the import statement that aliases the
module as "_" (import webhook_server.utils import helpers as _ ) to drop the
trailing "# noqa: F401" while keeping the inline comment that explains the
side-effect; no other code changes are needed.

In @webhook_server/utils/safe_rotating_handler.py:
- Around line 105-106: doRollover() calls self._open() unprotected when
resetting the stream (via "if not self.delay: self.stream = self._open()"),
which can raise OSError and break the "logging must NEVER crash" guarantee; wrap
the self._open() call in a try/except OSError block (matching the pattern used
elsewhere in doRollover), set self.stream to None on failure, and handle/log the
exception appropriately (e.g., via self.handleError or process-local logger) so
the method fails safely without propagating the OSError.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb242c3 and 1f480b0.

📒 Files selected for processing (3)
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/safe_rotating_handler.py
🧰 Additional context used
📓 Path-based instructions (2)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
🧠 Learnings (19)
📓 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.
📚 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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Do NOT use hasattr() checks for library versions we control in `pyproject.toml` (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely - return types can change (e.g., `Any` → `bool`), method signatures can be modified without deprecation

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., `user.node_id`, `user.type`, `sender`) - let KeyError surface legitimate bugs

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/tests/test_github_api.py
🧬 Code graph analysis (2)
webhook_server/utils/safe_rotating_handler.py (1)
webhook_server/libs/config.py (1)
  • exists (27-29)
webhook_server/tests/test_safe_rotating_handler.py (1)
webhook_server/utils/safe_rotating_handler.py (2)
  • SafeRotatingFileHandler (32-106)
  • doRollover (45-106)
🪛 Ruff (0.14.11)
webhook_server/tests/test_safe_rotating_handler.py

12-12: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)

webhook_server/tests/test_github_api.py

874-874: Unused method argument: minimal_hook_data

(ARG002)


1202-1202: Unused function argument: log_prefix

(ARG001)


1256-1256: Unused function argument: log_prefix

(ARG001)


1312-1312: Unused function argument: log_prefix

(ARG001)

🔇 Additional comments (20)
webhook_server/tests/test_github_api.py (9)

1-7: LGTM! Proper use of collections.abc for type imports.

Using Awaitable and Callable from collections.abc instead of typing is the modern Python 3.9+ approach. Good type hygiene.


46-53: LGTM! Fixture correctly updated for tag-only cloning behavior.

The docstring and ref change accurately reflect that only tag pushes (not branch pushes) trigger repository cloning. This aligns with the PushHandler logic that processes tags for PyPI upload and container builds.


72-106: LGTM! Improved type precision in fixtures.

The type hints now accurately reflect:

  • minimal_headers returning Headers (matching actual usage in tests)
  • Callable signatures with proper Awaitable[object] for async helpers
  • Return type union bool | dict[str, object] | None covering all branches

1269-1332: LGTM! Proper test for non-fatal git config warnings.

The test correctly validates that:

  1. Git config failures (user.name, user.email) are logged as warnings
  2. Clone operation succeeds despite config failures
  3. Exactly 2 warnings are logged (one per config command)

The mock signature with unused log_prefix is acceptable for mocks that don't need to inspect all parameters.


1511-1587: LGTM! Comprehensive test for tag checkout path.

Excellent test design:

  • Clear docstring explaining that checkout_ref is now only for tags (branch pushes skip cloning)
  • Flexible command matching using in checks to tolerate git flag variations
  • Descriptive assertion messages showing actual commands on failure

The command tracking pattern is clean and verifiable.


1588-1688: LGTM! Critical test for fetch ordering correctness.

This test validates an important invariant: the base branch must be fetched before the PR ref to ensure the local repository has the correct merge base. WHY order matters: fetching PR ref before base branch could result in incomplete history for merge operations.

The index-based ordering check at lines 1677-1687 is the right approach for verifying command sequence.


1775-1835: LGTM! Validates branch push optimization.

Good test coverage for the optimization where branch pushes skip cloning entirely. The docstring clearly explains the rationale: PushHandler only processes tags (PyPI upload, container build), so branch pushes have nothing to clone for.

Key assertions:

  • mock_clone.assert_not_called() - no cloning
  • mock_process_push.assert_not_called() - no handler invocation

1903-1964: LGTM! Essential test for config sanitization.

This test validates critical defensive behavior for the new configurable labels feature:

  1. Non-string entries (dict, list, int) are rejected and logged
  2. Warning message includes type info for debugging (dict(keys=, list(len=2), int()
  3. Valid string entries pass through to enabled_labels

The descriptive type representations in warnings will help users debug misconfigured YAML.


1837-1901: LGTM! Consistent type annotations for cleanup tests.

Type hints added match the pattern used throughout the file. Both tests properly validate:

  • test_cleanup: Successful directory removal via shutil.rmtree
  • test_cleanup_exception: Graceful handling of PermissionError with warning log
webhook_server/tests/test_safe_rotating_handler.py (6)

19-43: LGTM!

Solid test for basic rollover functionality. The test writes enough data to trigger multiple rollovers (20 records × 50 bytes each > 100 byte maxBytes), verifying the handler survives continuous operation. Proper cleanup with try/finally.


45-67: LGTM!

This test directly exercises the primary use case: rollover when backup files don't exist. The assertion handler.stream is not None confirms the handler remains functional after rollover.


69-107: LGTM!

Good test for the TOCTOU (time-of-check-to-time-of-use) race condition where os.path.exists() returns True but the file is deleted before os.remove() executes. The mock approach correctly simulates this scenario.


109-146: LGTM!

Covers the rename-during-rollover edge case. The mock simulates FileNotFoundError during rename operations for backup files while allowing normal rename for other files.


148-186: LGTM!

Well-designed test with good Windows portability consideration. The comment at lines 166-169 explains WHY the test uses mocking instead of actual file deletion—Windows doesn't allow removing files with open handles. This approach tests the same code path without platform-specific failures.


188-199: LGTM!

Essential integration test verifying the patch mechanism works. Since helpers is imported at module level (line 12), this confirms the monkey-patch is applied before any logger instances are created.

webhook_server/utils/safe_rotating_handler.py (5)

1-24: Excellent documentation.

The module docstring thoroughly explains WHY broad OSError suppression is the correct design choice. The explicit trade-off acknowledgment ("some rotation failures may go unnoticed") demonstrates thoughtful engineering. This prevents future maintainers from "fixing" the perceived bug of catching exceptions.


32-43: LGTM!

Clear class-level documentation that references the module docstring for detailed rationale. Good balance between brevity and completeness.


61-63: LGTM!

The type: ignore[assignment] is appropriate here. The base class types stream as TextIO | None depending on context, but mypy sees None as incompatible. The comment documents the intentional deviation.


65-81: LGTM!

The backup rotation loop correctly handles the race condition between os.path.exists() and file operations. The nested try/except with separate handlers for FileNotFoundError (common) and general OSError (edge cases) is clean.


83-103: LGTM!

Good separation between removing the .1 backup and rotating the base file. Both operations are protected from race conditions and general OSError.

Comment thread webhook_server/tests/test_github_api.py Outdated
Comment thread webhook_server/tests/test_safe_rotating_handler.py Outdated
Comment thread webhook_server/utils/safe_rotating_handler.py Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
- Fix mock_run_command signature to capture positional args
- Remove unused noqa: F401 directive
- Add OSError protection to _open() call in doRollover
- Remove unused minimal_hook_data fixture parameter
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webhook_server/tests/test_github_api.py (1)

1501-1505: LOW: Redundant Headers wrapping.

minimal_headers is already a Headers object (from fixture at line 73-74). Wrapping it again is unnecessary.

♻️ Suggested fix
             # Create webhook
             webhook = GithubWebhook(
                 hook_data=minimal_hook_data,
-                headers=Headers(minimal_headers),
+                headers=minimal_headers,
                 logger=logger,
             )
🤖 Fix all issues with AI agents
In @webhook_server/tests/test_github_api.py:
- Around line 1199-1204: The mock async function mock_run_command currently
declares a log_prefix parameter but doesn't use it, triggering Ruff ARG001; fix
by either renaming the parameter to _log_prefix: str in the mock_run_command
signature or by adding a discard assignment inside the function body like _ =
log_prefix so the parameter is explicitly used and the linter warning is
suppressed; update mock_run_command accordingly to match the handling used
elsewhere.
- Around line 1309-1314: The mock_run_command async function declares an unused
parameter log_prefix; rename it to _log_prefix (or prefix with an underscore) in
the mock_run_command signature to silence unused-parameter warnings and update
any references accordingly so the function remains async def
mock_run_command(command: str, _log_prefix: str, **_kwargs: object) ->
tuple[bool, str, str]: with no other behavior changes.
- Around line 1253-1258: The mock_run_command async function has an unused
parameter log_prefix; rename it to _log_prefix (or prefix it with an underscore)
in the mock_run_command signature to mirror the fix at line 1377 so linters
recognize it as intentionally unused, and update any local references
accordingly (keep function name mock_run_command and its return behavior
unchanged).

In @webhook_server/tests/test_safe_rotating_handler.py:
- Around line 221-232: The test TestSafeRotatingHandlerPatch is brittle because
it assumes webhook_server.utils.helpers was imported elsewhere to apply the
patch; explicitly import webhook_server.utils.helpers before asserting that
simple_logger.logger.RotatingFileHandler is SafeRotatingFileHandler so the
SafeRotatingFileHandler patch is applied deterministically (e.g., add an import
of webhook_server.utils.helpers at module top or at start of
test_simple_logger_uses_safe_handler).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f480b0 and 1baa0ab.

📒 Files selected for processing (3)
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/utils/safe_rotating_handler.py
🧰 Additional context used
📓 Path-based instructions (2)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
🧠 Learnings (21)
📓 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.
📚 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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/safe_rotating_handler.py
  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Code coverage must be 90% or higher - new code without tests fails CI

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Do NOT use hasattr() checks for library versions we control in `pyproject.toml` (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-09T09:16:45.452Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 579
File: webhook_server_container/libs/github_api.py:1098-1101
Timestamp: 2024-10-09T09:16:45.452Z
Learning: In the Python method `_run_tox` within `webhook_server_container/libs/github_api.py`, the variable `_tox_tests` is already comma-separated, so removing spaces with `_tox_tests.replace(" ", "")` is appropriate to handle any accidental spaces when specifying Tox environments.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-11-26T14:30:22.906Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 631
File: webhook_server_container/tests/test_github_api.py:321-327
Timestamp: 2024-11-26T14:30:22.906Z
Learning: In `webhook_server_container/tests/test_github_api.py`, when using `pytest.mark.parametrize` with indirect parameters, the `pytest.param` function may require nested lists to match the expected input structure.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/tests/test_github_api.py
🧬 Code graph analysis (2)
webhook_server/tests/test_safe_rotating_handler.py (1)
webhook_server/utils/safe_rotating_handler.py (2)
  • SafeRotatingFileHandler (32-111)
  • doRollover (45-111)
webhook_server/tests/test_github_api.py (2)
webhook_server/libs/config.py (2)
  • repository_local_data (89-130)
  • get_value (132-153)
webhook_server/libs/github_api.py (2)
  • GithubWebhook (80-878)
  • process (408-592)
🪛 Ruff (0.14.11)
webhook_server/tests/test_safe_rotating_handler.py

203-203: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/tests/test_github_api.py

1200-1200: Unused function argument: log_prefix

(ARG001)


1254-1254: Unused function argument: log_prefix

(ARG001)


1310-1310: Unused function argument: log_prefix

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: can-be-merged
  • GitHub Check: pre-commit
  • GitHub Check: tox
  • GitHub Check: conventional-title
  • GitHub Check: build-container
  • GitHub Check: python-module-install
🔇 Additional comments (14)
webhook_server/tests/test_github_api.py (6)

46-53: LGTM! Fixture correctly updated to reflect tag-based cloning behavior.

The docstring and payload now accurately document that only tag pushes (not branch pushes) trigger cloning. This aligns with the PR's changes where PushHandler only processes tags for PyPI upload and container builds.


1332-1398: LGTM! Comprehensive test for PR ref fetch failure.

This test properly validates that:

  1. RuntimeError is raised when PR ref fetch fails
  2. The error message contains the PR number
  3. Error logging occurs

Good use of the _ = log_prefix pattern at line 1377 to suppress the unused parameter warning.


1511-1586: LGTM! Good test coverage for tag checkout flow.

The test verifies the correct git commands are executed for tag-based cloning:

  1. Tag refspec fetch: refs/tags/v1.0.0:refs/tags/v1.0.0
  2. Tag checkout: checkout v1.0.0

The flexible matching approach using list comprehensions is robust against potential git flag variations.


1588-1687: LGTM! Excellent test for fetch ordering verification.

This test validates a critical invariant: base branch fetch must occur BEFORE PR ref fetch. The ordering check at lines 1677-1687 provides clear failure messages if the invariant is violated. This helps prevent subtle bugs where PR checkout might fail due to missing base branch.


1775-1835: LGTM! Test correctly updated to reflect branch push skip behavior.

The renamed test and updated docstring accurately reflect that branch pushes skip cloning entirely (because PushHandler only processes tags for PyPI/container builds). The assertions properly verify:

  1. _clone_repository is NOT called
  2. PushHandler.process_push_webhook_data is NOT called
  3. Returns None

1903-1963: LGTM! Good test coverage for configurable labels feature.

This test validates the new enabled-labels sanitization logic by:

  1. Providing mixed input types (string, dict, list, int)
  2. Verifying warning is logged with descriptive type info (dict(keys=..., list(len=2), int()
  3. Confirming only valid string entries are retained in enabled_labels

The assertions at lines 1955-1960 provide clear validation of the warning message content.

webhook_server/utils/safe_rotating_handler.py (4)

1-24: LGTM! Excellent documentation of design rationale.

The module docstring clearly explains WHY broad OSError suppression is intentional - logging infrastructure must never crash the application. This is a sound design choice for a logging handler, and the trade-off (some rotation failures may go unnoticed) is explicitly acknowledged.


45-63: LGTM! Clean stream handling with appropriate type annotation.

The type: ignore[assignment] on line 63 is necessary because RotatingFileHandler.stream isn't typed as Optional in the base class, but setting it to None is the correct behavior here. The method signature has proper return type annotation.


65-81: LGTM! Backup rotation handles TOCTOU races correctly.

The loop correctly iterates backwards to avoid overwriting files. The pattern of catching FileNotFoundError specifically (for documentation clarity) before the broader OSError is good practice. The TOCTOU race between exists() and the operation is acknowledged in comments and handled gracefully.


105-111: LGTM! Stream reopening gracefully degrades.

When _open() fails, leaving stream as None is correct behavior. As documented, FileHandler.emit() will attempt to reopen on the next log entry, providing automatic recovery.

webhook_server/tests/test_safe_rotating_handler.py (4)

18-42: LGTM! Good basic smoke test with proper resource cleanup.

The test correctly triggers multiple rollovers by writing enough data and uses try/finally to ensure the handler is closed. This validates the happy path before testing edge cases.


68-106: MEDIUM: Mock patches target global os module - works but is broader than necessary.

The patches on os.path.exists and os.remove work correctly since they modify the functions in the os module directly. However, for more targeted testing, you could patch at the import location: webhook_server.utils.safe_rotating_handler.os.path.exists.

Current approach is acceptable since tests are isolated and cleanup is handled properly.


147-184: LGTM! Windows compatibility handled correctly.

Good approach using mock_exists instead of actually deleting the file while the handler's stream is open. The comment on lines 165-168 correctly documents why this approach is necessary for cross-platform compatibility.


186-218: LGTM! Comprehensive failure and recovery test.

This test verifies both the failure scenario (stream is None after _open() fails) and the recovery scenario (handler remains usable after restoring normal behavior). The static analysis hint about TRY003 on line 203 is acceptable - in test code, inline exception messages for simulating errors are fine.

Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_github_api.py
Comment thread webhook_server/tests/test_safe_rotating_handler.py
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 13, 2026
- Fix mock_run_command parameter handling for log_prefix
- Add explicit helpers import for test isolation
- Remove redundant Headers() wrapping
- Revert shallow clone to fix merge unrelated histories bug
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

🤖 Fix all issues with AI agents
In @webhook_server/libs/github_api.py:
- Around line 697-731: Refactor the local helper _sanitize_item inside the
enabled-labels handling to use early returns for each branch (dict, list, else)
instead of nested if/elif/else flow: keep the same max_repr_len logic and output
formats (e.g., "dict(keys=...)", "list(len=...)", and truncating repr for other
types), preserve creation of sanitized_dropped and the subsequent
self.logger.warning calls, and do not change how enabled_set, invalid, or
self.enabled_labels are computed—this is purely a readability refactor of
_sanitize_item with no behavioral changes.

In @webhook_server/tests/test_safe_rotating_handler.py:
- Line 12: The import statement "import webhook_server.utils.helpers" is
intentionally done for side effects but the "noqa: F401" is unnecessary; remove
the "noqa: F401" directive and replace it with a brief inline comment after the
import explaining that the module is imported to patch simple_logger (e.g.,
"Imported for side effects: patches simple_logger"), leaving the import line
itself unchanged.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1baa0ab and 546857c.

📒 Files selected for processing (3)
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_safe_rotating_handler.py
🧰 Additional context used
📓 Path-based instructions (4)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Server fails-fast on startup if critical dependencies are missing - required parameters in __init__() are ALWAYS provided and checking for None on required parameters is pure overhead
Defensive checks in destructors (__del__) should use hasattr() to check for attributes that may not exist if initialization failed
Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes | None or Optional)
Defensive checks are acceptable for lazy initialization - attribute explicitly starts as None with type annotation attr: SomeType | None = None
Use hasattr() checks only for platform constants that may not exist on all platforms (e.g., os.O_NOFOLLOW)
Do NOT use hasattr() checks for required parameters in __init__() - if config is required, access it directly without defensive checks
Do NOT use hasattr() checks for library versions we control in pyproject.toml (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly
Use isinstance() for type discrimination instead of hasattr() checks
Never return fake defaults (empty strings, zeros, False, None, empty collections) to hide missing data - fail-fast with ValueError or KeyError exceptions
PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with asyncio.to_thread() to avoid blocking the event loop
When accessing PyGithub properties that may trigger API calls, wrap in asyncio.to_thread(lambda: obj.property) instead of direct access
When iterating PyGithub PaginatedList, wrap iteration in asyncio.to_thread(lambda: list(...)) to avoid blocking
Use asyncio.gather() for concurrent PyGithub operations to maximize performance
All imports must be at the top of files - no imports in functions or try/except blocks, except TYPE_CHECKING imports can be conditional
Complete type hints are mandatory - use mypy strict mode with full type annotations on function parameters and return types
Use logger.exception() for autom...

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/tests/**/*.py: Code coverage must be 90% or higher - new code without tests fails CI
Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens
Test coverage is required for new handlers - create handler tests in webhook_server/tests/test_*_handler.py

Files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Internal methods in webhook_server/libs/ can change freely - return types can change (e.g., Anybool), method signatures can be modified without deprecation

Files:

  • webhook_server/libs/github_api.py
webhook_server/libs/github_api.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/github_api.py: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification
Do NOT use defensive checks for webhook payload fields that are guaranteed to exist (e.g., user.node_id, user.type, sender) - let KeyError surface legitimate bugs
Repository cloning for check_run events should skip with early exit when action != 'completed'
Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Files:

  • webhook_server/libs/github_api.py
🧠 Learnings (30)
📓 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: 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.
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test coverage is required for new handlers - create handler tests in `webhook_server/tests/test_*_handler.py`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Code coverage must be 90% or higher - new code without tests fails CI

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema changes should be tested with `uv run pytest webhook_server/tests/test_config_schema.py -v`

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/tests/test_github_api.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/tests/test_safe_rotating_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.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/tests/test_safe_rotating_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.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/tests/test_safe_rotating_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/tests/test_safe_rotating_handler.py
  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handlers must use `self.github_webhook.unified_api` for all GitHub operations, not direct PyGithub calls

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely - return types can change (e.g., `Any` → `bool`), method signatures can be modified without deprecation

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When accessing PyGithub properties that may trigger API calls, wrap in `asyncio.to_thread(lambda: obj.property)` instead of direct access

Applied to files:

  • webhook_server/libs/github_api.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/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : PyGithub is synchronous/blocking - MUST wrap ALL method calls and property accesses with `asyncio.to_thread()` to avoid blocking the event loop

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : When iterating PyGithub PaginatedList, wrap iteration in `asyncio.to_thread(lambda: list(...))` to avoid blocking

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2025-02-25T12:01:42.999Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 666
File: webhook_server_container/libs/github_api.py:2254-2255
Timestamp: 2025-02-25T12:01:42.999Z
Learning: In the ProcessGithubWehook class, the `self.conventional_title` attribute is set during initialization by the `_repo_data_from_config()` method, and doesn't need additional existence checks.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2025-10-28T16:09:08.689Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 0
File: :0-0
Timestamp: 2025-10-28T16:09:08.689Z
Learning: For this repository, prioritize speed and minimizing API calls in reviews and suggestions: reuse webhook payload data, batch GraphQL queries, cache IDs (labels/users), and avoid N+1 patterns.

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler classes must implement `__init__(self, github_webhook, ...)` and `process_event(event_data)` pattern

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip when check_run_conclusion != 'success' and check_run_name == 'can-be-merged'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/libs/github_api.py : Repository cloning for check_run events should skip with early exit when action != 'completed'

Applied to files:

  • webhook_server/libs/github_api.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/app.py : Use `create_context()` to initialize webhook context in app.py with hook_id, event_type, repository, action, sender, api_user

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Access configuration using `Config(repository='org/repo-name')` and `config.get_value('setting-name', default_value)`

Applied to files:

  • webhook_server/libs/github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/tests/**/*.py : Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Do NOT use hasattr() checks for library versions we control in `pyproject.toml` (PyGithub >=2.4.0, gql >=3.5.0) - call methods directly

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-09T09:16:45.452Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 579
File: webhook_server_container/libs/github_api.py:1098-1101
Timestamp: 2024-10-09T09:16:45.452Z
Learning: In the Python method `_run_tox` within `webhook_server_container/libs/github_api.py`, the variable `_tox_tests` is already comma-separated, so removing spaces with `_tox_tests.replace(" ", "")` is appropriate to handle any accidental spaces when specifying Tox environments.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-11-26T14:30:22.906Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 631
File: webhook_server_container/tests/test_github_api.py:321-327
Timestamp: 2024-11-26T14:30:22.906Z
Learning: In `webhook_server_container/tests/test_github_api.py`, when using `pytest.mark.parametrize` with indirect parameters, the `pytest.param` function may require nested lists to match the expected input structure.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2026-01-05T14:43:44.055Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T14:43:44.055Z
Learning: Applies to webhook_server/**/*.py : Defensive checks are acceptable for optional parameters that explicitly allow None (parameter type includes `| None` or `Optional`)

Applied to files:

  • webhook_server/tests/test_github_api.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/tests/test_github_api.py
🧬 Code graph analysis (1)
webhook_server/tests/test_safe_rotating_handler.py (1)
webhook_server/utils/safe_rotating_handler.py (2)
  • SafeRotatingFileHandler (32-111)
  • doRollover (45-111)
🪛 Ruff (0.14.11)
webhook_server/tests/test_safe_rotating_handler.py

12-12: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


204-204: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/libs/github_api.py

321-321: Logging statement uses f-string

(G004)


329-329: Logging statement uses f-string

(G004)


342-342: Logging statement uses f-string

(G004)


343-343: Abstract raise to an inner function

(TRY301)


343-343: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Logging statement uses f-string

(G004)


355-355: Abstract raise to an inner function

(TRY301)


355-355: Avoid specifying long messages outside the exception class

(TRY003)


367-367: Logging statement uses f-string

(G004)


446-446: Logging statement uses f-string

(G004)


449-449: Logging statement uses f-string

(G004)


452-452: Logging statement uses f-string

(G004)


705-705: Dynamically typed expressions (typing.Any) are disallowed in item

(ANN401)


721-721: Logging statement uses f-string

(G004)


728-729: Logging statement uses f-string

(G004)

🔇 Additional comments (19)
webhook_server/tests/test_safe_rotating_handler.py (2)

16-219: Comprehensive test coverage for crash resilience scenarios.

The test class thoroughly covers the SafeRotatingFileHandler's error handling:

  • Basic rollover functionality (smoke test)
  • Missing backup files during rollover
  • TOCTOU race conditions (file deleted between exists() and operation)
  • FileNotFoundError during rename()
  • Base file deletion before rotate()
  • OSError during _open() with recovery verification

Good practices observed: proper try/finally cleanup, tempfile.TemporaryDirectory for isolation, and clear documentation about Windows compatibility (lines 166-169).


222-233: Good regression guard for the patching mechanism.

This test ensures the simple_logger patch remains in place. Since the patch happens at import time in helpers, this acts as a canary—if the patch mechanism breaks or the import order changes, this test will catch it immediately.

webhook_server/tests/test_github_api.py (8)

4-4: LGTM - Import additions for precise typing.

Adding Awaitable and Callable from collections.abc aligns with the typing refinements in github_api.py. These are the canonical locations for these types since Python 3.9+.


47-53: Fixture update correctly reflects new tag-only cloning semantics.

MEDIUM: The docstring and ref change from refs/heads/main to refs/tags/v1.0.0 accurately reflects that push events now only trigger cloning for tags, not branches. This aligns with the behavioral change in github_api.py where branch pushes skip cloning entirely.


72-74: Fixture type alignment with production code.

Returning Headers instead of dict ensures tests match the actual runtime behavior. Since GithubWebhook.__init__ expects Headers, this prevents false-positive test passes where a plain dict might work differently than the real Headers class.


80-87: Precise typing for async test helper.

LOW: The refined signature Callable[..., Awaitable[object]] is more accurate than using Any. The inner function's parameter types (Callable[..., object], *args: object, **kwargs: object) provide better type safety for test code.


1514-1589: Comprehensive test for tag checkout fetch path.

This test verifies the new tag-specific cloning behavior:

  1. Confirms the correct refspec is used for tag fetch (refs/tags/v1.0.0:refs/tags/v1.0.0)
  2. Verifies checkout command targets the correct tag name
  3. Uses flexible matching to tolerate additional git flags

The test correctly tracks command execution order and validates against expected patterns.


1591-1690: Critical test for PR fetch ordering invariant.

HIGH: This test validates an important correctness invariant - the base branch MUST be fetched before the PR ref. This ordering is critical because:

  • The base branch is needed for checkout
  • Fetching PR ref before base branch could cause checkout failures

The test verifies both the presence of correct fetch commands AND their relative ordering using index comparison.


1778-1838: Test confirms branch push skips cloning.

This test validates the optimization where branch pushes skip repository cloning entirely. The assertions verify:

  1. _clone_repository is NOT called
  2. PushHandler.process_push_webhook_data is NOT called
  3. Returns None

This is correct because PushHandler only processes tags (PyPI upload, container build), so branch pushes have no work to do.


1906-1967: Test for enabled-labels sanitization and logging.

This test covers an important defensive path - when users misconfigure enabled-labels with non-string entries (dicts, lists, integers from YAML mistakes), the code should:

  1. Filter out invalid entries
  2. Log a warning with sanitized representations
  3. Still process valid string entries

The assertions correctly verify:

  • Warning was logged with expected sanitization format (dict(keys=...), list(len=...), int(...))
  • Valid entries are preserved in enabled_labels
webhook_server/libs/github_api.py (9)

12-12: Type hint improvement for background tasks.

Using Task[Any] from the asyncio module directly is cleaner than the qualified asyncio.Task. This is consistent with PEP 585 style.

Also applies to: 87-87


294-295: Correct async wrapping for PyGithub property access.

HIGH: Per coding guidelines, PyGithub is synchronous/blocking and MUST be wrapped with asyncio.to_thread(). Using lambda: self.repository.clone_url correctly wraps the property access that may trigger an API call.


314-324: Owner login access properly wrapped.

The owner.login property access is wrapped with asyncio.to_thread to avoid blocking the event loop. The constructed git config commands use shell-safe quoting with single quotes around the login value.


331-367: Optimized fetch strategy reduces API overhead.

MEDIUM: The refactored fetch logic is significantly more efficient:

For PRs:

  1. Fetch only the base branch (needed for checkout)
  2. Fetch only the specific PR ref

For tags:

  • Fetch only the specific tag refspec

This replaces a blanket "fetch all remote refs" approach, reducing network traffic and GitHub API load. The error handling correctly raises RuntimeError for critical failures (base branch and PR ref fetches) while only warning for tag fetch failures.


356-367: Verify tag fetch failure is intentionally non-fatal.

The tag fetch failure only logs a warning (line 367) rather than raising an exception like the PR/base branch fetches. Is this intentional? If the tag fetch fails, the subsequent checkout will also fail.

Consider whether tag fetch failures should also be fatal:

             rc, _, _ = await run_command(
                 command=f"{git_cmd} fetch origin {fetch_refspec}",
                 log_prefix=self.log_prefix,
                 mask_sensitive=self.mask_sensitive,
             )
             if not rc:
-                self.logger.warning(f"{self.log_prefix} Failed to fetch tag {checkout_ref}")
+                redacted_err = redact_output(err)
+                self.logger.error(f"{self.log_prefix} Failed to fetch tag {checkout_ref}: {redacted_err}")
+                raise RuntimeError(f"Failed to fetch tag {checkout_ref}: {redacted_err}")

Note: If the warning is intentional (e.g., the tag may already exist locally from a previous clone), please disregard.


437-453: Branch push skip optimization is correct.

Only tag pushes require cloning because PushHandler exclusively processes:

  • PyPI uploads (triggered by tags)
  • Container builds (triggered by tags)

Branch pushes have no associated work in PushHandler, so skipping cloning saves resources. The logging clearly distinguishes between tag processing and branch skip paths.


619-625: Defensive type coercion for dict configs.

MEDIUM: The pattern _pypi if isinstance(_pypi, dict) else {} prevents AttributeError if a user misconfigures YAML with a string or list instead of a dict. This is acceptable defensive coding for optional parameters per coding guidelines.


656-671: Type coercion for list-valued config fields.

Same defensive pattern applied to list-valued configurations. This prevents TypeError when iterating if a user provides a string instead of a list.


684-739: Labels configuration loading with robust validation.

This implementation correctly handles the configurable labels feature:

  1. Merge strategy: Repo config overrides global config ({**global, **repo})
  2. None semantics: enabled_labels = None means all labels enabled (backward compatible)
  3. Non-string filtering: Protects against YAML mistakes (nested dicts/lists)
  4. Category validation: Only valid categories from CONFIGURABLE_LABEL_CATEGORIES are accepted
  5. Safe logging: Sanitizes dropped items to avoid logging large/untrusted YAML blobs

The sanitization helper _sanitize_item provides readable log output without exposing potentially large or sensitive config values.

Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/tests/test_safe_rotating_handler.py
@myakove
Copy link
Copy Markdown
Collaborator Author

myakove commented Jan 13, 2026

/build-and-push-container

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

@myakove-bot
Copy link
Copy Markdown
Collaborator

Successfully removed PR tag: ghcr.io/myk-org/github-webhook-server:pr-978.

@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.

feat(labels): Make labels configurable with enable/disable and custom colors

2 participants