Repository: myk-org/github-webhook-server
Pull request #729: Read repo configuration from .github-webhook-server.yaml in the repo
Author: login: myakove
Labels: name: size/L
name: branch-main
Created: 2025-04-25T07:29:41Z | Merged: 2025-04-25T08:41:08Z

Support read config from file locate in the repo.
Filename should be `.github-webhook-server.yaml` and locate in the root of the repo.

for example:

```yaml
minimum-lgtm: 1
can-be-merged-required-labels:
  - "my-label"
tox:
  main: all
set-auto-merge-prs:
  - dev
pre-commit: true
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Repository-specific configuration overrides via `.github-webhook-server.yaml` are now supported.
  - Introduced a `minimum-lgtm` setting to specify the minimum number of "LGTM" approvals required before merging.

- **Bug Fixes**
  - Pull request owner's LGTM label is excluded from counting towards the minimum LGTM requirement.

- **Documentation**
  - README updated with examples and instructions for repository-specific configuration overrides.

- **Tests**
  - Test coverage improved for repository-specific configs and minimum LGTM enforcement.
  - Test fixtures and function signatures updated to reflect new configuration handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.github-webhook-server.config b/.github-webhook-server.config
new file mode 100644
index 00000000..aa597f18
--- /dev/null
+++ b/.github-webhook-server.config
@@ -0,0 +1 @@
+minimum-lgtm: 1
diff --git a/README.md b/README.md
index 878ae675..f6576ef1 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,21 @@ repositories:
       main: []
 ```
 
+Repository config can be override by config file in the root on the repository named `.github-webhook-server.yaml`
+
+```yaml
+minimum-lgtm: 1
+can-be-merged-required-labels:
+  - "my-label"
+tox:
+  main: all
+set-auto-merge-prs:
+  - dev
+pre-commit: true
+```
+
+````
+
 - `github-app-id`: The ID of the GitHub app. Need to add the APP to the repository.
 - `name`: repository full name (org or user/repository name)
 - `webhook_ip`: Ip or FQDN where this app will run, this will be added as webhook in the repository setting
@@ -112,7 +127,7 @@ repositories:
 
 ```yaml
 slack_webhook_url: https://hooks.slack.com/services/<channel>
-```
+````
 
 if `pypi` configured for the repository a new version will be pushed to pypi on new GitHub release
 
diff --git a/webhook_server_container/libs/config.py b/webhook_server_container/libs/config.py
index df91988d..f6804b47 100644
--- a/webhook_server_container/libs/config.py
+++ b/webhook_server_container/libs/config.py
@@ -2,35 +2,69 @@
 from typing import Any
 
 import yaml
+from simple_logger.logger import get_logger
+
+LOGGER = get_logger(name="config")
 
 
 class Config:
-    def __init__(self, repository: str | None = None) -> None:
+    def __init__(self, repository: str | None = None, repository_full_name: str | None = None) -> None:
         self.data_dir: str = os.environ.get("WEBHOOK_SERVER_DATA_DIR", "/home/podman/data")
         self.config_path: str = os.path.join(self.data_dir, "config.yaml")
         self.exists()
         self.repository = repository
+        self.repository_full_name = repository_full_name
 
     def exists(self) -> None:
         if not os.path.isfile(self.config_path):
             raise FileNotFoundError(f"Config file {self.config_path} not found")
 
     @property
-    def data(self) -> dict[str, Any]:
-        with open(self.config_path) as fd:
-            return yaml.safe_load(fd)
+    def root_data(self) -> dict[str, Any]:
+        try:
+            with open(self.config_path) as fd:
+                return yaml.safe_load(fd)
+        except Exception:
+            LOGGER.error("Config file is empty")
+            return {}
 
     @property
     def repository_data(self) -> dict[str, Any]:
-        return self.data["repositories"].get(self.repository, {})
+        return self.root_data.get("repositories", {}).get(self.repository, {})
 
     def get_value(self, value: str, return_on_none: Any = None) -> Any:
         """
-        Get value from config, try first from repository and if not exists get it from root config
+        Get value from config
+
+        Order of getting value:
+            1. Local repository file (.github-webhook-server.yaml)
+            2. Repository level global config file (config.yaml)
+            3. Root level global config file (config.yaml)
         """
-        _val = self.repository_data.get(value)
 
-        if _val is None:
-            _val = self.data.get(value)
+        for scope in (self.repository_local_data, self.repository_data, self.root_data):
+            if value in scope:
+                value_data = scope[value]
+                if value_data is not None:
+                    return value_data
+
+        return return_on_none
+
+    @property
+    def repository_local_data(self) -> dict[str, Any]:
+        if self.repository and self.repository_full_name:
+            from webhook_server_container.utils.helpers import get_api_with_highest_rate_limit, get_github_repo_api
+
+            github_api, _, _ = get_api_with_highest_rate_limit(config=self, repository_name=self.repository)
+
+            if github_api:
+                try:
+                    repo = get_github_repo_api(github_api=github_api, repository=self.repository_full_name)
+                    _path = repo.get_contents(",github-webhook-server.yaml")
+                    config_file = _path[0] if isinstance(_path, list) else _path
+                    yaml.safe_load(config_file.decoded_content)
+
+                except Exception:
+                    return {}
 
-        return _val or return_on_none
+        return {}
diff --git a/webhook_server_container/libs/github_api.py b/webhook_server_container/libs/github_api.py
index 180decdc..63f83146 100644
--- a/webhook_server_container/libs/github_api.py
+++ b/webhook_server_container/libs/github_api.py
@@ -2109,7 +2109,7 @@ def _check_if_pr_approved(self, labels: list[str]) -> str:
         if self.minimum_lgtm:
             for _label in labels:
                 reviewer = _label.split(LABELS_SEPARATOR)[-1]
-                if LGTM_BY_LABEL_PREFIX.lower() in _label.lower() and reviewer in all_reviewers:
+                if LGTM_BY_LABEL_PREFIX.lower() in _label.lower() and reviewer in all_reviewers_without_pr_owner:
                     lgtm_count += 1
 
         for _label in labels:
diff --git a/webhook_server_container/tests/conftest.py b/webhook_server_container/tests/conftest.py
index a7a560e3..278fd1e0 100644
--- a/webhook_server_container/tests/conftest.py
+++ b/webhook_server_container/tests/conftest.py
@@ -112,6 +112,7 @@ def process_github_webhook(mocker, request):
     process_github_webhook.pull_request_branch = "main"
     if hasattr(request, "param") and request.param:
         process_github_webhook.changed_files = request.param[0]
+
     else:
         process_github_webhook.changed_files = ALL_CHANGED_FILES
 
diff --git a/webhook_server_container/tests/manifests/config.yaml b/webhook_server_container/tests/manifests/config.yaml
index b300b259..8f9295e3 100644
--- a/webhook_server_container/tests/manifests/config.yaml
+++ b/webhook_server_container/tests/manifests/config.yaml
@@ -74,3 +74,5 @@ repositories:
     can-be-merged-required-labels: # check for extra labels to set PR as can be merged
       - my-label1
       - my-label2
+
+    minimum-lgtm: 0
diff --git a/webhook_server_container/tests/test_branch_protection.py b/webhook_server_container/tests/test_branch_protection.py
index 029b191d..ce3ecc40 100644
--- a/webhook_server_container/tests/test_branch_protection.py
+++ b/webhook_server_container/tests/test_branch_protection.py
@@ -10,19 +10,26 @@
 
 @pytest.fixture()
 def branch_protection_rules(request, mocker):
+    config_path = "webhook_server_container.libs.config.Config"
     os.environ["WEBHOOK_SERVER_DATA_DIR"] = "webhook_server_container/tests/manifests"
     repo_name = "test-repo"
     config = Config(repository=repo_name)
-    data = config.data
-    data.setdefault("branch_protection", request.param.get("global", {}))
-    data["repositories"][repo_name].setdefault("branch_protection", request.param.get("repo"))
+    root_data = config.root_data
+    root_data.setdefault("branch_protection", request.param.get("global", {}))
+    root_data["repositories"][repo_name].setdefault("branch_protection", request.param.get("repo"))
+
+    mocker.patch(f"{config_path}.root_data", new_callable=mocker.PropertyMock, return_value=root_data)
+
     mocker.patch(
-        "webhook_server_container.libs.config.Config.data", new_callable=mocker.PropertyMock, return_value=data
+        f"{config_path}.repository_data",
+        new_callable=mocker.PropertyMock,
+        return_value=root_data["repositories"][repo_name],
     )
+
     mocker.patch(
-        "webhook_server_container.libs.config.Config.repository_data",
+        f"{config_path}.repository_local_data",
         new_callable=mocker.PropertyMock,
-        return_value=data["repositories"][repo_name],
+        return_value={},
     )
     return get_repo_branch_protection_rules(config=config)
 
diff --git a/webhook_server_container/tests/test_pull_request_owners.py b/webhook_server_container/tests/test_pull_request_owners.py
index fcfe3c6a..af28d430 100644
--- a/webhook_server_container/tests/test_pull_request_owners.py
+++ b/webhook_server_container/tests/test_pull_request_owners.py
@@ -139,7 +139,7 @@ def test_all_approvers_reviewers(process_github_webhook, all_approvers_and_revie
     assert all_reviewers == process_github_webhook.all_reviewers
 
 
-def test_check_pr_approved(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved(process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
 
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
@@ -157,7 +157,7 @@ def test_check_pr_approved(process_github_webhook, all_approvers_and_reviewers):
     assert check_if_pr_approved == ""
 
 
-def test_check_pr_minimum_approved(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_minimum_approved(process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -170,7 +170,7 @@ def test_check_pr_minimum_approved(process_github_webhook, all_approvers_and_rev
     assert check_if_pr_approved == ""
 
 
-def test_check_pr_not_approved(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_not_approved(process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -191,7 +191,7 @@ def test_check_pr_not_approved(process_github_webhook, all_approvers_and_reviewe
     assert missing_approvers == expected_approvers
 
 
-def test_check_pr_partial_approved(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_partial_approved(process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -226,7 +226,9 @@ def test_check_pr_partial_approved(process_github_webhook, all_approvers_and_rev
     ],
     indirect=True,
 )
-def test_check_pr_approved_specific_folder(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_specific_folder(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -250,7 +252,9 @@ def test_check_pr_approved_specific_folder(process_github_webhook, all_approvers
     ],
     indirect=True,
 )
-def test_check_pr_approved_nested_folder_no_owners(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_nested_folder_no_owners(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -271,7 +275,9 @@ def test_check_pr_approved_nested_folder_no_owners(process_github_webhook, all_a
     ],
     indirect=True,
 )
-def test_check_pr_approved_specific_folder_with_root_approvers(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_specific_folder_with_root_approvers(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -299,7 +305,9 @@ def test_check_pr_approved_specific_folder_with_root_approvers(process_github_we
     ],
     indirect=True,
 )
-def test_check_pr_approved_specific_folder_no_root_approvers(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_specific_folder_no_root_approvers(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -320,7 +328,9 @@ def test_check_pr_approved_specific_folder_no_root_approvers(process_github_webh
     ],
     indirect=True,
 )
-def test_check_pr_not_approved_specific_folder_without_owners(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_not_approved_specific_folder_without_owners(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -348,7 +358,9 @@ def test_check_pr_not_approved_specific_folder_without_owners(process_github_web
     ],
     indirect=True,
 )
-def test_check_pr_approved_specific_folder_without_owners(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_specific_folder_without_owners(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -371,7 +383,7 @@ def test_check_pr_approved_specific_folder_without_owners(process_github_webhook
     indirect=True,
 )
 def test_check_pr_approved_folder_with_no_owners_and_folder_without_root_approvers(
-    process_github_webhook, all_approvers_and_reviewers
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
 ):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
@@ -396,7 +408,7 @@ def test_check_pr_approved_folder_with_no_owners_and_folder_without_root_approve
     indirect=True,
 )
 def test_check_pr_not_approved_folder_with_no_owners_and_folder_without_root_approvers(
-    process_github_webhook, all_approvers_and_reviewers
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
 ):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
@@ -425,7 +437,9 @@ def test_check_pr_not_approved_folder_with_no_owners_and_folder_without_root_app
     ],
     indirect=True,
 )
-def test_check_pr_not_approved_subfolder_with_owners(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_not_approved_subfolder_with_owners(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
@@ -453,7 +467,9 @@ def test_check_pr_not_approved_subfolder_with_owners(process_github_webhook, all
     ],
     indirect=True,
 )
-def test_check_pr_approved_subfolder_with_owners_no_root_approvers(process_github_webhook, all_approvers_and_reviewers):
+def test_check_pr_approved_subfolder_with_owners_no_root_approvers(
+    process_github_webhook, all_approvers_and_reviewers, all_approvers_reviewers
+):
     process_github_webhook.all_approvers = process_github_webhook.get_all_approvers()
     check_if_pr_approved = process_github_webhook._check_if_pr_approved(
         labels=[
diff --git a/webhook_server_container/tests/test_repo_data_from_config.py b/webhook_server_container/tests/test_repo_data_from_config.py
index cef45e5a..5bca191d 100644
--- a/webhook_server_container/tests/test_repo_data_from_config.py
+++ b/webhook_server_container/tests/test_repo_data_from_config.py
@@ -18,3 +18,4 @@ def test_repo_data_from_config_repository_found(process_github_webhook):
     assert process_github_webhook.pre_commit
     assert process_github_webhook.auto_verified_and_merged_users == ["my[bot]"]
     assert process_github_webhook.can_be_merged_required_labels == ["my-label1", "my-label2"]
+    assert process_github_webhook.minimum_lgtm == 0
diff --git a/webhook_server_container/utils/github_repository_and_webhook_settings.py b/webhook_server_container/utils/github_repository_and_webhook_settings.py
index 4e6d2eae..5f68b0a1 100644
--- a/webhook_server_container/utils/github_repository_and_webhook_settings.py
+++ b/webhook_server_container/utils/github_repository_and_webhook_settings.py
@@ -26,7 +26,7 @@ def get_repository_api(repository: str) -> tuple[str, github.Github | None, str]
 
     apis: list = []
     with ThreadPoolExecutor() as executor:
-        for repo, data in config.data["repositories"].items():
+        for repo, data in config.root_data["repositories"].items():
             apis.append(
                 executor.submit(
                     get_repository_api,
diff --git a/webhook_server_container/utils/github_repository_settings.py b/webhook_server_container/utils/github_repository_settings.py
index fdc362c9..fe1742f4 100644
--- a/webhook_server_container/utils/github_repository_settings.py
+++ b/webhook_server_container/utils/github_repository_settings.py
@@ -200,7 +200,7 @@ def set_repositories_settings(config: Config, apis_dict: dict[str, dict[str, Any
     logger = get_logger_with_params(name="github-repository-settings")
 
     logger.info("Processing repositories")
-    config_data = config.data
+    config_data = config.root_data
 
     docker: dict[str, str] | None = config_data.get("docker")
     if docker:
@@ -330,7 +330,7 @@ def set_all_in_progress_check_runs_to_queued(repo_config: Config, apis_dict: dic
     futures: list["Future"] = []
 
     with ThreadPoolExecutor() as executor:
-        for repo, data in repo_config.data["repositories"].items():
+        for repo, data in repo_config.root_data["repositories"].items():
             repo_config = Config(repository=repo)
             futures.append(
                 executor.submit(
@@ -393,7 +393,7 @@ def get_repository_github_app_api(config_: Config, repository_name: str) -> Gith
     with open(os.path.join(config_.data_dir, "webhook-server.private-key.pem")) as fd:
         private_key = fd.read()
 
-    github_app_id: int = config_.data["github-app-id"]
+    github_app_id: int = config_.root_data["github-app-id"]
     auth: AppAuth = Auth.AppAuth(app_id=github_app_id, private_key=private_key)
     app_instance: GithubIntegration = GithubIntegration(auth=auth)
     owner: str
diff --git a/webhook_server_container/utils/helpers.py b/webhook_server_container/utils/helpers.py
index b8d5f015..235520af 100644
--- a/webhook_server_container/utils/helpers.py
+++ b/webhook_server_container/utils/helpers.py
@@ -140,7 +140,7 @@ def get_api_with_highest_rate_limit(
         repository_name (str, optional): Repository name, if provided try to get token set in config repository section.
 
     Returns:
-        tuple: API, token
+        tuple: API, token, api_user
     """
     logger = get_logger_with_params(name="helpers")
 
diff --git a/webhook_server_container/utils/webhook.py b/webhook_server_container/utils/webhook.py
index 1c1e31c4..a597e578 100644
--- a/webhook_server_container/utils/webhook.py
+++ b/webhook_server_container/utils/webhook.py
@@ -56,11 +56,11 @@ def process_github_webhook(
 
 def create_webhook(config: Config, apis_dict: dict[str, dict[str, Any]]) -> None:
     LOGGER.info("Preparing webhook configuration")
-    webhook_ip = config.data["webhook_ip"]
+    webhook_ip = config.root_data["webhook_ip"]
 
     futures = []
     with ThreadPoolExecutor() as executor:
-        for repo, data in config.data["repositories"].items():
+        for repo, data in config.root_data["repositories"].items():
             futures.append(
                 executor.submit(
                     process_github_webhook,


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🔭 Outside diff range comments (3)</summary><blockquote>

<details>
<summary>.github-webhook-server.config (1)</summary><blockquote>

`1-2`: _⚠️ Potential issue_

**Filename doesn't match PR description.**

The PR description mentions using `.github-webhook-server.yaml` as the configuration file, but this file is named `.github-webhook-server.config`. This inconsistency could cause confusion for users following the documentation.


Additionally, the example in the PR description includes more configuration options like `can-be-merged-required-labels` and `tox` parameters, but this file only contains `minimum-lgtm`. Consider adding comments about the other supported configuration options for better discoverability.

</blockquote></details>
<details>
<summary>webhook_server_container/tests/test_branch_protection.py (1)</summary><blockquote>

`19-21`: _⚠️ Potential issue_

**Inconsistent mocking strategy.**

While line 16 was updated to use `root_data`, this mock still references the old `Config.data` property. It should be updated to mock `Config.root_data` instead to maintain consistency with the refactoring.


Apply this diff to fix the issue:
```diff
 mocker.patch(
-    "webhook_server_container.libs.config.Config.data", new_callable=mocker.PropertyMock, return_value=data
+    "webhook_server_container.libs.config.Config.root_data", new_callable=mocker.PropertyMock, return_value=data
 )
```

</blockquote></details>
<details>
<summary>webhook_server_container/utils/helpers.py (1)</summary><blockquote>

`143-173`: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**`api_user` and `rate_limit` captured from the *last* token, not from the chosen one**  

Inside the loop you update `api`, `token` *and* `remaining` when a higher limit is found, but the auxiliary variables `rate_limit` and `_api_user` are **not** updated in that same `if` block.  
Consequences:  
* The information logged by `log_rate_limit(…)` and the info-level summary may display the wrong user/limit.  
* The tuple returned (`api, token, _api_user`) can contain a mismatching `api_user`, breaking callers that rely on it.

```diff
for _api, _token in apis_and_tokens:
     cur_user = _api.get_user().login
     cur_limit = _api.get_rate_limit()
     if cur_limit.core.remaining > remaining:
         remaining = cur_limit.core.remaining
         api, token = _api, _token
-        _api_user = cur_user            # << keep in sync
-        rate_limit = cur_limit          # << keep in sync
+        _api_user = cur_user
+        rate_limit = cur_limit
```

Also consider renaming `_api_user` to `selected_api_user` to avoid confusion with the loop variable.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>webhook_server_container/utils/github_repository_settings.py (1)</summary><blockquote>

`333-334`: **Iteration source switched to `root_data`; verify recursion performance**  

Good catch replacing `repo_config.data` with `root_data`, but this now reloads the YAML file once per iteration because `root_data` re-opens the file on each access. For dozens of repos that’s unnecessary I/O.

Recommend caching once:

```python
repositories_cfg = repo_config.root_data.get("repositories", {})
for repo, data in repositories_cfg.items():
     ...
```

</blockquote></details>
<details>
<summary>webhook_server_container/libs/config.py (1)</summary><blockquote>

`20-27`: **Repeated file reads in `root_data` property**  

Every access re-opens `config.yaml`. Consider memoising for performance:

```diff
-@property
-def root_data(self) -> dict[str, Any]:
-    with open(self.config_path) as fd:
-        return yaml.safe_load(fd)
+@property
+def root_data(self) -> dict[str, Any]:
+    if not hasattr(self, "_root_cache"):
+        with open(self.config_path) as fd:
+            self._root_cache = yaml.safe_load(fd) or {}
+    return self._root_cache
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a542c3e3406477e15fe27a897b6501f24eadfb51 and ed3b2779df9a574a1023d4fa6f0cf6606a5504d7.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `.github-webhook-server.config` (1 hunks)
* `webhook_server_container/libs/config.py` (1 hunks)
* `webhook_server_container/tests/test_branch_protection.py` (1 hunks)
* `webhook_server_container/utils/github_repository_and_webhook_settings.py` (1 hunks)
* `webhook_server_container/utils/github_repository_settings.py` (3 hunks)
* `webhook_server_container/utils/helpers.py` (1 hunks)
* `webhook_server_container/utils/webhook.py` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (5)</summary>

<details>
<summary>webhook_server_container/utils/github_repository_and_webhook_settings.py (1)</summary><blockquote>

<details>
<summary>webhook_server_container/libs/config.py (1)</summary>

* `root_data` (20-22)

</details>

</blockquote></details>
<details>
<summary>webhook_server_container/tests/test_branch_protection.py (1)</summary><blockquote>

<details>
<summary>webhook_server_container/libs/config.py (1)</summary>

* `root_data` (20-22)

</details>

</blockquote></details>
<details>
<summary>webhook_server_container/utils/webhook.py (1)</summary><blockquote>

<details>
<summary>webhook_server_container/libs/config.py (1)</summary>

* `root_data` (20-22)

</details>

</blockquote></details>
<details>
<summary>webhook_server_container/utils/github_repository_settings.py (1)</summary><blockquote>

<details>
<summary>webhook_server_container/libs/config.py (1)</summary>

* `root_data` (20-22)

</details>

</blockquote></details>
<details>
<summary>webhook_server_container/libs/config.py (2)</summary><blockquote>

<details>
<summary>webhook_server_container/utils/helpers.py (2)</summary>

* `get_api_with_highest_rate_limit` (132-173)
* `get_github_repo_api` (41-42)

</details>
<details>
<summary>webhook_server_container/tests/conftest.py (1)</summary>

* `get_contents` (57-94)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: build-container
* GitHub Check: python-module-install
* GitHub Check: pre-commit
* GitHub Check: tox

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>webhook_server_container/utils/github_repository_and_webhook_settings.py (1)</summary>

`29-29`: **Consistent usage of root_data property.**

The change from `config.data` to `config.root_data` aligns with the refactoring to support repository-specific configuration files.

</details>
<details>
<summary>webhook_server_container/tests/test_branch_protection.py (1)</summary>

`16-16`: **Change to use root_data is correct.**

The change from `config.data` to `config.root_data` aligns with the refactoring for layered configuration.

</details>
<details>
<summary>webhook_server_container/utils/webhook.py (2)</summary>

`59-59`: **Consistent usage of root_data property.**

The change from `config.data` to `config.root_data` aligns with the refactoring to support repository-specific configuration files.

---

`63-63`: **Consistent usage of root_data property.**

The change from `config.data` to `config.root_data` aligns with the refactoring to support repository-specific configuration files.

</details>
<details>
<summary>webhook_server_container/utils/github_repository_settings.py (1)</summary>

`396-396`: **Hard failure on missing `github-app-id`**  

`config_.root_data["github-app-id"]` will raise a `KeyError` if not configured. Prefer a safer access with an explanatory error:

```python
github_app_id = config_.root_data.get("github-app-id")
if github_app_id is None:
    logger.error("'github-app-id' is missing from configuration")
    return None
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T07:33:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>webhook_server_container/libs/config.py (1)</summary><blockquote>

`54-73`: _⚠️ Potential issue_

**Risk of infinite recursion and incorrect file handling in `repository_local_data`.**

The implementation has several issues:

1. Potential infinite recursion: `get_api_with_highest_rate_limit` likely calls `get_value`, which then calls `repository_local_data`.
2. Incorrect file handling: `open(config_file.content)` attempts to open a path, but `content` is a base64-encoded string.
3. Overly broad exception handling: Silently returning an empty dict hides configuration issues.




Reimplement the method to avoid recursion and handle the file content correctly:

```diff
+from base64 import b64decode
+import github
...
@property
def repository_local_data(self) -> dict[str, Any]:
    if not (self.repository and self.repository_full_name):
        return {}

+   # Avoid circular dependency: fetch tokens directly from root config
+   tokens = self.root_data.get("github-tokens", [])
+   if not tokens:
+       return {}
+
+   # Create a GitHub API instance with the first token
+   api = github.Github(auth=github.Auth.Token(tokens[0]))
    
-   from webhook_server_container.utils.helpers import get_api_with_highest_rate_limit, get_github_repo_api
-   github_api, _, _ = get_api_with_highest_rate_limit(config=self, repository_name=self.repository)
+   from webhook_server_container.utils.helpers import get_github_repo_api

-   if github_api:
+   try:
-       try:
-           repo = get_github_repo_api(github_api=github_api, repository=self.repository_full_name)
+       repo = get_github_repo_api(github_api=api, repository=self.repository_full_name)
        config_file = repo.get_contents(".github-webhook-server.yaml")
        config_file = config_file[0] if isinstance(config_file, list) else config_file

-       with open(config_file.content) as fd:
-           return yaml.safe_load(fd)
+       decoded = b64decode(config_file.content)
+       return yaml.safe_load(decoded) or {}

-       except Exception:
-           return {}
+   except github.GithubException as exc:
+       LOGGER.warning(f"Unable to load local repo config: {exc}")
+       return {}
+   except Exception as exc:
+       LOGGER.error(f"Unexpected error loading local repo config: {exc}")
+       return {}

    return {}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>README.md (3)</summary><blockquote>

`105-105`: **Fix grammatical error in configuration override description.**

"Repository config can be override" is grammatically incorrect.


```diff
-Repository config can be override by config file in the root on the repository named `.github-webhook-server.yaml`
+Repository config can be overridden by a config file in the root of the repository named `.github-webhook-server.yaml`
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~105-~105: This verb may not be in the correct form. Consider using a different form for this context.
Context: ... main: [] ```  Repository config can be override by config file in the root on the repos...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

</details>

</details>

---

`118-119`: **Fix Markdown code block syntax.**

The closing code block marker uses four backticks (`````) instead of three (```), which can cause formatting issues.


```diff
-````
+```
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

118-118: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`130-130`: **Fix Markdown code block syntax.**

The closing code block marker uses four backticks (`````) instead of three (```), which can cause formatting issues.


```diff
-````
+```
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ed3b2779df9a574a1023d4fa6f0cf6606a5504d7 and 401be4c1c9a3f6418f15459282aa170cd37ed992.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `README.md` (2 hunks)
* `webhook_server_container/libs/config.py` (1 hunks)
* `webhook_server_container/libs/github_api.py` (1 hunks)
* `webhook_server_container/tests/conftest.py` (1 hunks)
* `webhook_server_container/tests/manifests/config.yaml` (1 hunks)
* `webhook_server_container/tests/test_branch_protection.py` (1 hunks)
* `webhook_server_container/tests/test_pull_request_owners.py` (14 hunks)
* `webhook_server_container/tests/test_repo_data_from_config.py` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* webhook_server_container/tests/conftest.py
* webhook_server_container/tests/manifests/config.yaml

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* webhook_server_container/tests/test_branch_protection.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>webhook_server_container/libs/github_api.py (1)</summary>

```
Learnt from: myakove
PR: myk-org/github-webhook-server#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.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>webhook_server_container/tests/test_repo_data_from_config.py (1)</summary><blockquote>

<details>
<summary>webhook_server_container/tests/conftest.py (1)</summary>

* `process_github_webhook` (98-119)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~105-~105: This verb may not be in the correct form. Consider using a different form for this context.
Context: ... main: [] ```  Repository config can be override by config file in the root on the repos...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

118-118: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: build-container
* GitHub Check: python-module-install
* GitHub Check: pre-commit
* GitHub Check: tox

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>webhook_server_container/tests/test_repo_data_from_config.py (1)</summary>

`21-21`: **Effective addition of minimum_lgtm validation.**

The test now verifies that the `minimum_lgtm` property is correctly set to 2, which aligns with the new configuration key added in the repository-specific config for the test repo.

</details>
<details>
<summary>webhook_server_container/libs/github_api.py (1)</summary>

`2112-2112`: **Good enhancement: Exclude PR owner's LGTM from count.**

This change prevents PR authors from counting their own LGTM votes toward the required minimum, enhancing code review integrity.

</details>
<details>
<summary>webhook_server_container/libs/config.py (5)</summary>

`5-7`: **Clean import and logger setup.**

Proper implementation of the `simple_logger` import and configuration for module-level logging.

---

`11-11`: **Good design for flexible repository configuration.**

Adding the `repository_full_name` parameter allows for more flexible repository configuration, facilitating the new repository-local configuration feature.



Also applies to: 16-16

---

`23-29`: **Improved error handling for root configuration.**

Renaming `data` to `root_data` and adding error handling for empty config files improves robustness.

---

`33-33`: **Streamlined repository data access.**

The updated property correctly accesses repository-specific configuration from the root data.

---

`37-51`: **Layered configuration prioritization.**

The `get_value` method now implements a clear precedence order for configuration values, which is well-documented in the docstring.

</details>
<details>
<summary>webhook_server_container/tests/test_pull_request_owners.py (1)</summary>

`142-142`: **Consistent test integration with the new configuration system.**

The addition of the `all_approvers_reviewers` fixture to all test functions aligns with the PR's goal of supporting layered configuration, particularly the `minimum-lgtm` setting that can now be specified in a repository-local `.github-webhook-server.yaml` file. This fixture works alongside the existing `all_approvers_and_reviewers` fixture to provide comprehensive test data for the approval logic.




Also applies to: 160-160, 173-173, 194-194, 229-231, 255-257, 278-280, 308-310, 331-333, 361-363, 386-386, 411-411, 440-442, 470-472

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T08:10:20Z

Review comments:
diff_hunk: @@ -200,7 +200,7 @@ def set_repositories_settings(config: Config, apis_dict: dict[str, dict[str, Any
     logger = get_logger_with_params(name="github-repository-settings")
 
     logger.info("Processing repositories")
-    config_data = config.data
+    config_data = config.root_data
 
     docker: dict[str, str] | None = config_data.get("docker")
path: webhook_server_container/utils/github_repository_settings.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Potential `KeyError` when `repositories` key missing**  

`config_data = config.root_data` is fine, but immediately accessing `config_data["repositories"]` assumes that the key is always present. When the new layered configuration kicks in, users may forget to provide the section in the global file if they rely on per-repo configs.  

```diff
-repositories_cfg = config_data["repositories"]
+repositories_cfg = config_data.get("repositories", {})
+if not repositories_cfg:
+    logger.warning("No 'repositories' section found in global config; skipping repository processing")
+    return
```

Failing early with a clear message avoids a cryptic traceback.  



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T07:33:41Z
author_association: NONE
diff_hunk: @@ -5,32 +5,62 @@
 
 
 class Config:
-    def __init__(self, repository: str | None = None) -> None:
+    def __init__(self, repository: str | None = None, repository_full_name: str | None = None) -> None:
         self.data_dir: str = os.environ.get("WEBHOOK_SERVER_DATA_DIR", "/home/podman/data")
         self.config_path: str = os.path.join(self.data_dir, "config.yaml")
         self.exists()
         self.repository = repository
+        self.repository_full_name = repository_full_name
 
     def exists(self) -> None:
         if not os.path.isfile(self.config_path):
             raise FileNotFoundError(f"Config file {self.config_path} not found")
 
     @property
-    def data(self) -> dict[str, Any]:
+    def root_data(self) -> dict[str, Any]:
         with open(self.config_path) as fd:
             return yaml.safe_load(fd)
 
     @property
     def repository_data(self) -> dict[str, Any]:
-        return self.data["repositories"].get(self.repository, {})
+        return self.root_data["repositories"].get(self.repository, {})
 
     def get_value(self, value: str, return_on_none: Any = None) -> Any:
         """
-        Get value from config, try first from repository and if not exists get it from root config
+        Get value from config
+
+        Order of getting value:
+            1. Local repository file (.github-webhook-server.yaml)
+            2. Repository level global config file (config.yaml)
+            3. Root level global config file (config.yaml)
         """
-        _val = self.repository_data.get(value)
+        _val = self.repository_local_data.get(value)
 
         if _val is None:
-            _val = self.data.get(value)
+            _val = self.repository_data.get(value)
+
+            if _val is None:
+                _val = self.root_data.get(value)
 
         return _val or return_on_none
path: webhook_server_container/libs/config.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`get_value` risks infinite recursion and is O(N²)**  

Because the first lookup executes `self.repository_local_data`, and that property eventually calls `get_api_with_highest_rate_limit` → `self.get_value`, you can end up in an endless loop (`RecursionError`).  
Break the cycle by letting `repository_local_data` access *only* root-level data for the GitHub token:

```python
tokens = self.root_data.get("github-tokens", [])
```

Additionally, build a simple linear precedence search to avoid re-entering the whole method:

```diff
for scope in (self.repository_local_data,
              self.repository_data,
              self.root_data):
    if value in scope:
        return scope[value]
return return_on_none
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T07:33:42Z
author_association: NONE
diff_hunk: @@ -5,32 +5,62 @@
 
 
 class Config:
-    def __init__(self, repository: str | None = None) -> None:
+    def __init__(self, repository: str | None = None, repository_full_name: str | None = None) -> None:
         self.data_dir: str = os.environ.get("WEBHOOK_SERVER_DATA_DIR", "/home/podman/data")
         self.config_path: str = os.path.join(self.data_dir, "config.yaml")
         self.exists()
         self.repository = repository
+        self.repository_full_name = repository_full_name
 
     def exists(self) -> None:
         if not os.path.isfile(self.config_path):
             raise FileNotFoundError(f"Config file {self.config_path} not found")
 
     @property
-    def data(self) -> dict[str, Any]:
+    def root_data(self) -> dict[str, Any]:
         with open(self.config_path) as fd:
             return yaml.safe_load(fd)
 
     @property
     def repository_data(self) -> dict[str, Any]:
-        return self.data["repositories"].get(self.repository, {})
+        return self.root_data["repositories"].get(self.repository, {})
 
     def get_value(self, value: str, return_on_none: Any = None) -> Any:
         """
-        Get value from config, try first from repository and if not exists get it from root config
+        Get value from config
+
+        Order of getting value:
+            1. Local repository file (.github-webhook-server.yaml)
+            2. Repository level global config file (config.yaml)
+            3. Root level global config file (config.yaml)
         """
-        _val = self.repository_data.get(value)
+        _val = self.repository_local_data.get(value)
 
         if _val is None:
-            _val = self.data.get(value)
+            _val = self.repository_data.get(value)
+
+            if _val is None:
+                _val = self.root_data.get(value)
 
         return _val or return_on_none
+
+    @property
+    def repository_local_data(self) -> dict[str, Any]:
+        if self.repository and self.repository_full_name:
+            from webhook_server_container.utils.helpers import get_api_with_highest_rate_limit, get_github_repo_api
+
+            github_api, _, _ = get_api_with_highest_rate_limit(config=self, repository_name=self.repository)
+
+            if github_api:
+                try:
+                    repo = get_github_repo_api(github_api=github_api, repository=self.repository_full_name)
+                    config_file = repo.get_contents(".github-webhook-server.yaml")
+                    config_file = config_file[0] if isinstance(config_file, list) else config_file
+
+                    with open(config_file.content) as fd:
+                        return yaml.safe_load(fd)
+
+                except Exception:
+                    return {}
+
+        return {}
path: webhook_server_container/libs/config.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Multiple problems in `repository_local_data` implementation**

1. 🌀 **Infinite recursion** – triggers through `get_value` as explained above.  
2. 🗄️ **Wrong content access** – `open(config_file.content)` tries to open a *path*, but `content` is a base-64 string.  
3. 📉 **Silent failure** – blanket `except Exception` swallows all errors, hiding configuration issues.

Suggested re-implementation:

```diff
from base64 import b64decode
...
def repository_local_data(self) -> dict[str, Any]:
    if not (self.repository and self.repository_full_name):
        return {}

    # Avoid circular dependency: fetch tokens directly from root config
    tokens = self.root_data.get("github-tokens", [])
    if not tokens:
        return {}

    from webhook_server_container.utils.helpers import get_github_repo_api
    api = github.Github(auth=github.Auth.Token(tokens[0]))  # pick first token
    repo = get_github_repo_api(api, self.repository_full_name)

    try:
        cfg_file = repo.get_contents(".github-webhook-server.yaml")
        cfg_file = cfg_file[0] if isinstance(cfg_file, list) else cfg_file
-        with open(config_file.content) as fd:
-            return yaml.safe_load(fd)
+        decoded = b64decode(cfg_file.content)
+        return yaml.safe_load(decoded) or {}
    except github.GithubException as exc:
        raise RuntimeError(f"Unable to load local repo config: {exc}") from exc
```

This removes recursion, decodes the file correctly, and surfaces actionable errors.  
 


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T07:33:42Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-25T07:29:47Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

This change introduces support for layered configuration in the GitHub webhook server. A new `.github-webhook-server.config` file is added, specifying a `minimum-lgtm` setting. The `Config` class is refactored to support reading configuration values from three sources in order of precedence: a repository-local `.github-webhook-server.yaml` file, repository-level config, and root-level config. Code throughout the project is updated to use the renamed `root_data` property instead of `data`, and test fixtures are adjusted accordingly. Additionally, a function's return type annotation is updated to include an extra value.

## Changes

| Files/Paths                                                                                  | Change Summary                                                                                                                                                                                                                                   |
|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.github-webhook-server.config`                                                             | Added new configuration file with `minimum-lgtm: 1` setting.                                                                                                                                                                                     |
| `webhook_server_container/libs/config.py`                                                   | Refactored `Config` class to support layered config retrieval, added `repository_local_data` property, renamed `data` to `root_data`, and updated constructor signature.                                                                         |
| `webhook_server_container/tests/test_branch_protection.py`                                  | Updated test fixture to use `config.root_data` instead of `config.data`.                                                                                                                                                                         |
| `webhook_server_container/utils/github_repository_and_webhook_settings.py`<br>`.../github_repository_settings.py`<br>`.../webhook.py` | Updated all config data accesses from `data` to `root_data` for consistency with new config structure.                                                                                                     |
| `webhook_server_container/utils/helpers.py`                                                 | Updated return type annotation and docstring for `get_api_with_highest_rate_limit` to include `api_user` as a third tuple element.                                                                         |
| `webhook_server_container/libs/github_api.py`                                              | Modified LGTM counting logic to exclude the pull request owner from counting towards minimum LGTM approvals.                                                                                              |
| `README.md`                                                                                | Documented repository-local config override via `.github-webhook-server.yaml` and fixed YAML code block formatting.                                                                                          |
| `webhook_server_container/tests/test_pull_request_owners.py`                               | Added `all_approvers_reviewers` fixture parameter to multiple test functions.                                                                                                                                                                      |
| `webhook_server_container/tests/test_repo_data_from_config.py`                             | Added assertion verifying `minimum_lgtm` value is set correctly in test.                                                                                                                                                                           |
| `webhook_server_container/tests/conftest.py`                                               | Added blank line after assignment in fixture for formatting only.                                                                                                                                                                                 |

## Possibly related issues

- myk-org/github-webhook-server#730: Implements reading repository configuration from a local `.github-webhook-server.yaml` file, which is directly addressed by the new layered config logic in this PR.

## Possibly related PRs

- myk-org/github-webhook-server#720: This PR’s config file and layered retrieval logic directly support the approval logic enhancements implemented in #720.
- myk-org/github-webhook-server#727: Both PRs refine LGTM counting and PR approval logic in `libs/github_api.py`.
- myk-org/github-webhook-server#684: Both PRs modify config data access and handling of GitHub API/user information, showing strong code-level overlap.

## Suggested labels

`verified`, `can-be-merged`, `approved-myakove`

## Suggested reviewers

- rnetser
"""

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d64d9207a49b94d246632f0e85fc18e28e2febd5 and f3c6a1112952deeaebf0bf70cb9f447f70bea166.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `webhook_server_container/libs/config.py` (1 hunks)
* `webhook_server_container/tests/test_branch_protection.py` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* webhook_server_container/tests/test_branch_protection.py
* webhook_server_container/libs/config.py

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: tox
* GitHub Check: build-container
* GitHub Check: python-module-install
* GitHub Check: pre-commit

</details>

</details>
<!-- internal state start -->


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

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=myk-org/github-webhook-server&utm_content=729):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: myakove-bot
created_at: 2025-04-25T07:29:48Z
body: 
Report bugs in [Issues](https://github.com/myakove/github-webhook-server/issues)

The following are automatically added:
 * Add reviewers from OWNER file (in the root of the repository) under reviewers section.
 * Set PR size label.
 * New issue is created for the PR. (Closed when PR is merged/closed)
 * Run [pre-commit](https://pre-commit.ci/) if `.pre-commit-config.yaml` exists in the repo.

Available user actions:
 * To mark PR as WIP comment `/wip` to the PR, To remove it from the PR comment `/wip cancel` to the PR.
 * To block merging of PR comment `/hold`, To un-block merging of PR comment `/hold cancel`.
 * To mark PR as verified comment `/verified` to the PR, to un-verify comment `/verified cancel` to the PR.
        verified label removed on each new commit push.
 * To cherry pick a merged PR comment `/cherry-pick <target branch to cherry-pick to>` in the PR.
    * Multiple target branches can be cherry-picked, separated by spaces. (`/cherry-pick branch1 branch2`)
    * Cherry-pick will be started when PR is merged
 * To build and push container image command `/build-and-push-container` in the PR (tag will be the PR number).
    * You can add extra args to the Podman build command
        * Example: `/build-and-push-container --build-arg OPENSHIFT_PYTHON_WRAPPER_COMMIT=<commit_hash>`
 * To add a label by comment use `/<label name>`, to remove, use `/<label name> cancel`
 * To assign reviewers based on OWNERS file use `/assign-reviewers`
 * To check if PR can be merged use `/check-can-merge`
 * to assign reviewer to PR use `/assign-reviewer @<reviewer>`

PR will be approved when the following conditions are met:
 * `/approve` from one of the approvers.
 * Minimum number of required `/lgtm` (`1`) is met.

<details>
<summary>Approvers and Reviewers</summary>


 * Approvers:
   * myakove
   * rnetser


 * Reviewers:
   * myakove
   * rnetser


</details>

<details>
<summary>Supported /retest check runs</summary>

 * `/retest tox`: Retest tox
 * `/retest build-container`: Retest build-container
 * `/retest python-module-install`: Retest python-module-install
 * `/retest pre-commit`: Retest pre-commit
 * `/retest all`: Retest all

</details>

<details>
<summary>Supported labels</summary>

 * hold
 * verified
 * wip
 * lgtm
 * approve

</details>
    
author_association: COLLABORATOR