Skip to content

Support triggering commands via review assignment in Gitlab#2388

Open
igoraj wants to merge 2 commits into
The-PR-Agent:mainfrom
igoraj:feature/gitlab-reviewer-assignment-trigger
Open

Support triggering commands via review assignment in Gitlab#2388
igoraj wants to merge 2 commits into
The-PR-Agent:mainfrom
igoraj:feature/gitlab-reviewer-assignment-trigger

Conversation

@igoraj
Copy link
Copy Markdown

@igoraj igoraj commented May 12, 2026

Gitlab comes with built-it in commands /request_review, /reviewer, /assign_reviewer which all pop-up when one tries to write /review in comment.

I thought it might come handy if assigning reviewer would actually trigger the same thing, as it is now by default ignored.

I made the change as an togglable setting via handle_reviewer_assignment which is off by default so it does not break current behavior under [gitlab] section.
In addition one can set reviewer_commands = ["/review"] array optionally triggering several commands upon assignment.

Example use in config:

[gitlab]
handle_reviewer_assignment = true
reviewer_commands = ["/review", "/improve"]

…b MR

Add support for detecting when the PR-Agent bot is assigned as a
reviewer on a GitLab merge request via the webhook payload, and
automatically running configured commands (e.g. /review) in response.

- Add handle_reviewer_assignment toggle and reviewer_commands config
  under [gitlab] in configuration.toml (disabled by default)
- Add is_bot_assigned_as_reviewer() to parse changes.reviewers from
  the webhook payload and detect initial bot assignment
- Add _get_bot_user_id() to auto-resolve the bot's GitLab user ID via
  API, with per-process caching
- Add new dispatch branch in the merge_request event handler for
  reviewer-assignment updates
- Call apply_repo_settings() before reading the toggle, enabling
  per-project configuration via .pr_agent.toml

Assisted-by: opencode:deepseek-v4-pro
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Support auto-triggering commands on GitLab reviewer assignment

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add GitLab reviewer assignment detection via webhook payload
• Auto-trigger configured commands when bot assigned as reviewer
• Implement bot user ID resolution with per-process caching
• Add configurable toggle and command list for reviewer assignment
Diagram
flowchart LR
  A["GitLab Webhook<br/>MR Update Event"] -->|"Check action=update"| B["is_bot_assigned_as_reviewer()"]
  B -->|"Parse reviewers<br/>changes"| C["_get_bot_user_id()"]
  C -->|"Resolve via API<br/>with cache"| D["Bot ID Match"]
  D -->|"If enabled"| E["apply_repo_settings()"]
  E -->|"handle_reviewer_assignment=true"| F["Execute<br/>reviewer_commands"]
  F -->|"Run /review etc"| G["Process MR"]
Loading

Grey Divider

File Changes

1. pr_agent/servers/gitlab_webhook.py ✨ Enhancement +45/-0

Add reviewer assignment detection and command triggering

• Add _get_bot_user_id() function to resolve bot's GitLab user ID via API with global caching
• Add is_bot_assigned_as_reviewer() function to detect when bot is newly assigned as reviewer by
 comparing previous and current reviewer lists
• Add new dispatch branch in merge_request event handler to trigger reviewer_commands when bot
 assignment detected
• Call apply_repo_settings() before checking toggle to enable per-project configuration

pr_agent/servers/gitlab_webhook.py


2. pr_agent/settings/configuration.toml ⚙️ Configuration changes +5/-0

Add GitLab reviewer assignment configuration options

• Add handle_reviewer_assignment boolean toggle (default: false) under [gitlab] section
• Add reviewer_commands array configuration to specify commands triggered on reviewer assignment
• Default reviewer_commands includes "/review" command

pr_agent/settings/configuration.toml


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 12, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (5)

Grey Divider


Action required

1. Permanent failure cache 🐞 Bug ☼ Reliability ⭐ New
Description
_get_bot_user_id() caches a sentinel (-1) on any exception and then returns None for all future
calls for that URL/token, so one transient GitLab auth/network failure can permanently disable
reviewer-assignment triggering until process restart.
Code

pr_agent/servers/gitlab_webhook.py[R122-151]

+    cache_key = hashlib.sha256(f"{gitlab_url}:{gitlab_token}".encode()).hexdigest()
+
+    cached = _bot_user_id_cache.get(cache_key)
+    if cached is not None:
+        return cached if cached != -1 else None
+
+    def _resolve_sync():
+        import gitlab
+
+        ssl_verify = get_settings().get("GITLAB.SSL_VERIFY", True)
+        auth_method = get_settings().get("GITLAB.AUTH_TYPE", "oauth_token")
+        kwargs = {"url": gitlab_url, "ssl_verify": ssl_verify}
+        if auth_method == "oauth_token":
+            kwargs["oauth_token"] = gitlab_token
+        else:
+            kwargs["private_token"] = gitlab_token
+
+        gl = gitlab.Gitlab(**kwargs)
+        gl.auth()
+        return gl.user.id
+
+    try:
+        user_id = await asyncio.to_thread(_resolve_sync)
+        _bot_user_id_cache[cache_key] = user_id
+        get_logger().info(f"Bot user ID resolved via API: {user_id}")
+        return user_id
+    except Exception as e:
+        _bot_user_id_cache[cache_key] = -1
+        get_logger().error(f"Failed to resolve bot user ID: {e}")
+        return None
Evidence
The function explicitly stores -1 on exception and the cache lookup treats -1 as a permanent
"return None" outcome, with no TTL/expiration mechanism anywhere in the cache handling.

pr_agent/servers/gitlab_webhook.py[113-151]

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

### Issue description
`_get_bot_user_id()` stores `-1` in `_bot_user_id_cache` on *any* exception, and subsequent calls treat that as a permanent failure (returning `None`). This makes reviewer-assignment triggering brittle: a transient outage/rate-limit/timeout can disable the feature until the service is restarted.

### Issue Context
The cache is process-global and has no expiration logic. The code currently uses `-1` as a negative-cache sentinel with no retry window.

### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[113-151]

### Suggested fix
- Replace the `-1` sentinel with a small structure like `{value, ts, is_failure}` and only honor negative-cache entries for a short TTL (e.g., 30–300 seconds), after which a retry is allowed.
- Alternatively, do not cache failures at all (or cache only specific non-retryable failures), and keep caching only successful resolutions.
- Add/adjust a unit test to verify retries occur after TTL expiry (or that failures are not cached).

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


2. Late import after env 📘 Rule violation ⚙ Maintainability
Description
The test sets GITLAB__URL at module import time and then performs a module-level import of
pr_agent.servers.gitlab_webhook, triggering Ruff E402 and making test behavior depend on ambient
environment state. This violates lint/style conventions and the requirement that tests explicitly
control environment variables/process-global state for determinism.
Code

tests/unittest/test_gitlab_reviewer_assignment.py[R4-6]

+os.environ.setdefault("GITLAB__URL", "https://gitlab.example.com")
+import pr_agent.servers.gitlab_webhook as gitlab_webhook
+
Evidence
Rule 10 requires adherence to Ruff conventions; the file executes code
(os.environ.setdefault(...)) before a module-level import, which commonly triggers Ruff E402 and
breaks import-order conventions. Rule 17 requires tests to explicitly control env/global state;
using setdefault at import time does not guarantee the test’s preconditions and can vary depending
on the runner’s environment.

AGENTS.md
tests/unittest/test_gitlab_reviewer_assignment.py[4-6]
Best Practice: Learned patterns

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

## Issue description
`tests/unittest/test_gitlab_reviewer_assignment.py` executes `os.environ.setdefault(...)` at import time and then imports `pr_agent.servers.gitlab_webhook` at module scope, which triggers Ruff `E402` and makes the test rely on implicit/ambient environment state.
## Issue Context
`pr_agent.servers.gitlab_webhook` performs work at import time (e.g., logger setup using `get_settings()`), so tests that need env/config should set env vars explicitly inside a fixture/test, then import/reload the module deterministically.
## Fix Focus Areas
- tests/unittest/test_gitlab_reviewer_assignment.py[1-6]

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


3. Patch targets missing gitlab ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The unit tests patch pr_agent.servers.gitlab_webhook.gitlab.Gitlab, but gitlab_webhook.py is a
single module (not a package with a gitlab submodule) and the production code imports gitlab
only locally inside _get_bot_user_id(), so there is no module-level gitlab attribute to patch
and mock.patch(...) will raise AttributeError/import errors before assertions run. This breaks
the test suite/CI and undermines the requirement to provide working test coverage for the changed
behavior.
Code

tests/unittest/test_gitlab_reviewer_assignment.py[R93-100]

+        with mock.patch("pr_agent.servers.gitlab_webhook.get_settings", return_value=make_settings("https://a.example.com", "token-a")):
+            with mock.patch("pr_agent.servers.gitlab_webhook.gitlab.Gitlab", mock_gitlab_cls):
+                assert gitlab_webhook._get_bot_user_id() == 111
+
+        gl_mock.user.id = 222
+        with mock.patch("pr_agent.servers.gitlab_webhook.get_settings", return_value=make_settings("https://a.example.com", "token-b")):
+            with mock.patch("pr_agent.servers.gitlab_webhook.gitlab.Gitlab", mock_gitlab_cls):
+                assert gitlab_webhook._get_bot_user_id() == 222
Evidence
Rule 13 requires relevant tests for the changed behavior, but the tests currently target
pr_agent.servers.gitlab_webhook.gitlab.Gitlab, which unittest.mock.patch interprets as importing
pr_agent.servers.gitlab_webhook.gitlab and then replacing Gitlab; the codebase instead has
pr_agent/servers/gitlab_webhook.py (a file), so that import path does not exist. In production,
_get_bot_user_id() performs import gitlab inside the function and then calls
gitlab.Gitlab(...), meaning gitlab is not a module-level attribute on
pr_agent.servers.gitlab_webhook, so the patch fails before the tests can execute; consequently,
patching gitlab.Gitlab (or injecting a fake gitlab module) is the correct approach.

AGENTS.md
tests/unittest/test_gitlab_reviewer_assignment.py[93-100]
pr_agent/servers/gitlab_webhook.py[128-141]
pr_agent/servers/gitlab_webhook.py[1-13]

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

## Issue description
The unit tests currently patch `"pr_agent.servers.gitlab_webhook.gitlab.Gitlab"`, but `gitlab_webhook.py` is a module (not a package with a `gitlab` submodule) and `gitlab` is imported locally inside `_get_bot_user_id()`, so there is no `pr_agent.servers.gitlab_webhook.gitlab` import path (nor a module-level `gitlab` attribute) for `mock.patch` to replace. This causes the patch to fail before assertions run, breaking CI and preventing the tests from providing valid coverage.
## Issue Context
`_get_bot_user_id()` does `import gitlab` and then uses `gitlab.Gitlab(...)`. To avoid requiring the real `python-gitlab` dependency during tests, update the tests to patch the correct target (`gitlab.Gitlab`) or inject a fake `gitlab` module into `sys.modules` (e.g., via `monkeypatch` or `mock.patch.dict`) that provides a `Gitlab` class returning the desired mock client.
## Fix Focus Areas
- tests/unittest/test_gitlab_reviewer_assignment.py[89-100]
- tests/unittest/test_gitlab_reviewer_assignment.py[114-119]
- tests/unittest/test_gitlab_reviewer_assignment.py[134-145]
- pr_agent/servers/gitlab_webhook.py[128-141]

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


View more (5)
4. Unconditional repo settings fetch ✓ Resolved 🐞 Bug ➹ Performance
Description
The reviewer-assignment webhook path calls apply_repo_settings(url) before checking
handle_reviewer_assignment, so even when the feature is disabled (default), MR update events
without oldrev still trigger GitLabProvider construction and GitLab API work. This adds avoidable
latency and rate-limit/load risk on common non-reviewer update events.
Code

pr_agent/servers/gitlab_webhook.py[R321-326]

+            elif object_attributes.get('action') == 'update' and not object_attributes.get('oldrev'):
+                url = object_attributes.get('url')
+                apply_repo_settings(url)
+                if not get_settings().gitlab.get('handle_reviewer_assignment', False):
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
Evidence
The new branch calls apply_repo_settings(url) before the feature flag check.
apply_repo_settings() constructs a Git provider via get_git_provider_with_context(), and the
GitLab provider constructor immediately sets the MR and loads diffs (GitLab API calls), so this
becomes expensive work executed even when the feature is off.

pr_agent/servers/gitlab_webhook.py[320-334]
pr_agent/git_providers/utils.py[14-27]
pr_agent/git_providers/init.py[40-65]
pr_agent/git_providers/gitlab_provider.py[32-76]
pr_agent/git_providers/gitlab_provider.py[341-346]

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

## Issue description
`apply_repo_settings(url)` is invoked unconditionally in the reviewer-assignment update branch, before the feature toggle check. `apply_repo_settings()` constructs a Git provider instance, and for GitLab this initializes a `GitLabProvider` which fetches MR data/diffs via the GitLab API.
## Issue Context
This code runs for `merge_request` webhooks where `action == "update"` and `oldrev` is falsy, which can occur for many non-reviewer updates (title/label/etc). When `handle_reviewer_assignment` is false (the default), the handler should return without fetching repo settings/provider.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[320-334]
- pr_agent/git_providers/utils.py[14-27]
- pr_agent/git_providers/__init__.py[40-65]
- pr_agent/git_providers/gitlab_provider.py[32-76]
- pr_agent/git_providers/gitlab_provider.py[341-346]
### Suggested approach
1) Add a cheap payload guard before any expensive work (e.g., if `changes.reviewers` missing -> return).
2) Check the instance-level/global setting first (e.g., `get_settings().get("gitlab.handle_reviewer_assignment", False)`); if false and repo-settings-file is not used, return.
3) Only then call `apply_repo_settings(url)` and re-check the setting (to support repo overrides), and proceed to `is_bot_assigned_as_reviewer` / `_perform_commands_gitlab`.

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


5. New strings use single quotes ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
Newly added code uses single-quoted strings (e.g., dict keys like data.get('changes')), which
conflicts with the compliance requirement to use double quotes for strings. This can cause avoidable
Ruff formatting/lint failures.
Code

pr_agent/servers/gitlab_webhook.py[R150-167]

+def is_bot_assigned_as_reviewer(data) -> bool:
+    try:
+        changes = data.get('changes')
+        if not isinstance(changes, dict):
+            return False
+        if 'reviewers' not in changes:
+            return False
+        reviewers_change = changes['reviewers']
+        if not isinstance(reviewers_change, dict):
+            return False
+        previous = reviewers_change.get('previous', [])
+        current = reviewers_change.get('current', [])
+        bot_user_id = _get_bot_user_id()
+        if bot_user_id is None:
+            return False
+        previous_ids = {r.get('id') for r in previous if isinstance(r, dict)}
+        current_ids = {r.get('id') for r in current if isinstance(r, dict)}
+        return bot_user_id in current_ids and bot_user_id not in previous_ids
Evidence
PR Compliance ID 6 explicitly requires double quotes for strings. The added reviewer-assignment
logic introduces multiple single-quoted string literals (e.g., data.get('changes'),
'reviewers'), violating that requirement.

AGENTS.md
pr_agent/servers/gitlab_webhook.py[150-167]

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

## Issue description
New code uses single-quoted strings, but the compliance checklist requires double quotes for Python strings.
## Issue Context
This affects newly added GitLab reviewer-assignment logic and may trigger Ruff formatting/lint failures.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[150-167]
- pr_agent/servers/gitlab_webhook.py[320-333]

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


6. _bot_user_id_cache shared global state ✓ Resolved 📘 Rule violation ⛨ Security
Description
_get_bot_user_id() caches a token-derived GitLab user ID in the process-global
_bot_user_id_cache, which can persist across webhook requests and silently apply the wrong
identity when tokens/instances differ. This can cause incorrect authorization/behavior (e.g.,
reviewer-assignment detection misfiring) and cross-request state leakage in multi-tenant,
rotated-token, or warm AWS Lambda deployments.
Code

pr_agent/servers/gitlab_webhook.py[R111-126]

+_bot_user_id_cache = None
+
+def _get_bot_user_id():
+    global _bot_user_id_cache
+    if _bot_user_id_cache is not None:
+        return _bot_user_id_cache
+    try:
+        import gitlab
+        gl = gitlab.Gitlab(
+            get_settings().get("GITLAB.URL", "https://gitlab.com"),
+            private_token=get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None),
+        )
+        gl.auth()
+        _bot_user_id_cache = gl.user.id
+        get_logger().info(f"Bot user ID resolved via API: {_bot_user_id_cache}")
+        return _bot_user_id_cache
Evidence
PR Compliance ID 18 requires avoiding shared mutable globals for provider scoping and guarding
against silent overrides, yet the code introduces _bot_user_id_cache as a module-global value that
is populated from an authenticated GitLab client and then reused for future calls regardless of
request context. Because the webhook path can set different GitLab tokens per request based on
X-Gitlab-Token (via a secret provider), and AWS Lambda can reuse the same module/router across
warm invocations, the single cached ID can outlive a request and incorrectly apply to subsequent
requests using different credentials or GitLab instances.

pr_agent/servers/gitlab_webhook.py[111-126]
pr_agent/servers/gitlab_webhook.py[111-129]
pr_agent/servers/gitlab_webhook.py[207-243]
pr_agent/servers/gitlab_lambda_webhook.py[2-7]
Best Practice: Learned patterns

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

## Issue description
`_bot_user_id_cache` is a module-level cache for a value derived from GitLab credentials (the current user/bot id). In a server handling multiple webhook requests—potentially with different tokens, tenants, or GitLab instances—this shared mutable global can persist across requests (and warm AWS Lambda invocations) and cause incorrect bot identity decisions, including reviewer-assignment detection triggering for the wrong user or failing to trigger.
## Issue Context
The webhook handler can set GitLab credentials dynamically per request (e.g., via secret provider and `X-Gitlab-Token`). The bot user id must be scoped to the effective credentials (and ideally the GitLab URL/instance) rather than stored in a single process-wide variable; otherwise, the cached ID can silently mismatch the current request’s token/instance, violating the intent of PR Compliance ID 18 (avoid shared mutable globals for provider scoping and protect against silent overrides).
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[111-129]
- pr_agent/servers/gitlab_webhook.py[207-243]
- pr_agent/servers/gitlab_lambda_webhook.py[2-7]

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


7. No tests for reviewer assignment 📘 Rule violation ⚙ Maintainability
Description
The PR adds new behavior to auto-trigger commands when the bot is assigned as a reviewer, but no
corresponding pytest coverage is added/updated. This increases regression risk for webhook parsing
and configuration toggle behavior.
Code

pr_agent/servers/gitlab_webhook.py[R296-302]

+            # for reviewer assignment triggered merge requests
+            elif object_attributes.get('action') == 'update' and is_bot_assigned_as_reviewer(data):
+                url = object_attributes.get('url')
+                apply_repo_settings(url)
+                if get_settings().gitlab.get('handle_reviewer_assignment', False):
+                    get_logger().info(f"Bot was assigned as reviewer on MR: {url}")
+                    await _perform_commands_gitlab("reviewer_commands", PRAgent(), url, log_context, data)
Evidence
PR Compliance ID 13 requires behavior changes to be backed by tests in the appropriate pytest
directories. The diff introduces a new webhook action path that performs commands on reviewer
assignment, but this PR diff does not include any new/updated tests for it.

AGENTS.md
pr_agent/servers/gitlab_webhook.py[296-303]

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

## Issue description
Reviewer-assignment-triggered command execution is new behavior and needs tests to prevent regressions.
## Issue Context
Add unit tests to validate:
- detection logic for bot assignment based on `changes.reviewers.previous/current`
- `handle_reviewer_assignment` gating
- correct command list used (`reviewer_commands`)
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[296-303]

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


8. Auth type ignored ✓ Resolved 🐞 Bug ≡ Correctness
Description
_get_bot_user_id always authenticates using private_token and does not pass SSL verification
settings, while the rest of the GitLab integration supports oauth_token/private_token selection via
GITLAB.AUTH_TYPE and SSL_VERIFY. This can prevent bot user-id resolution in oauth_token deployments
and silently disable reviewer-assignment triggering.
Code

pr_agent/servers/gitlab_webhook.py[R118-126]

+        import gitlab
+        gl = gitlab.Gitlab(
+            get_settings().get("GITLAB.URL", "https://gitlab.com"),
+            private_token=get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None),
+        )
+        gl.auth()
+        _bot_user_id_cache = gl.user.id
+        get_logger().info(f"Bot user ID resolved via API: {_bot_user_id_cache}")
+        return _bot_user_id_cache
Evidence
The new bot-id resolver hardcodes private_token without ssl_verify, while the existing GitLab
provider explicitly supports oauth/private tokens via GITLAB.AUTH_TYPE and uses SSL_VERIFY,
demonstrating expected configuration behavior.

pr_agent/servers/gitlab_webhook.py[113-126]
pr_agent/git_providers/gitlab_provider.py[33-65]

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

## Issue description
`_get_bot_user_id()` hardcodes `private_token=...` and omits `ssl_verify`, diverging from `GitLabProvider` which selects auth based on `GITLAB.AUTH_TYPE` and configures SSL verification.
### Issue Context
If `GITLAB.AUTH_TYPE` is set to (or defaults to) oauth token usage, the hardcoded `private_token` path can fail and `is_bot_assigned_as_reviewer()` will always return `False`.
### Fix
- Mirror the `GitLabProvider` client creation logic:
- read `GITLAB.AUTH_TYPE` and pass either `oauth_token=` or `private_token=` accordingly
- pass `ssl_verify=get_settings().get("GITLAB.SSL_VERIFY", True)`
- Prefer extracting a shared helper to create an authenticated python-gitlab client to avoid drift.
### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[113-126]
- pr_agent/git_providers/gitlab_provider.py[33-65]

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



Remediation recommended

9. object_attributes assumed dict 📘 Rule violation ☼ Reliability ⭐ New
Description
The new reviewer-assignment branch calls object_attributes.get(...) without confirming
object_attributes is a dict. If GitLab sends object_attributes as null/non-dict for an
update event, this path can raise and skip processing unexpectedly.
Code

pr_agent/servers/gitlab_webhook.py[R324-325]

+            elif object_attributes.get('action') == 'update' and not object_attributes.get('oldrev'):
+                url = object_attributes.get('url')
Evidence
Rule 19 requires defensive access patterns for optional/variable external structures. The new code
uses object_attributes.get(...) in the reviewer-assignment branch without a preceding type
check/normalization of object_attributes.

pr_agent/servers/gitlab_webhook.py[323-326]
Best Practice: Learned patterns

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

## Issue description
The reviewer-assignment path assumes `object_attributes` is a dict and calls `.get()` on it without validation.

## Issue Context
Webhook payloads are external/variable structures. The new `elif object_attributes.get(...)` branch can raise if `object_attributes` is missing or not a dict.

## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[323-326]

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


10. ssl_verify not normalized 📘 Rule violation ⛨ Security ⭐ New
Description
GITLAB.SSL_VERIFY is passed through to the GitLab client without validation/normalization, so
string/env overrides (e.g., "false") may be interpreted incorrectly and change TLS verification
behavior. This can cause misconfiguration risk and potentially weaken transport security depending
on how the downstream library interprets the value.
Code

pr_agent/servers/gitlab_webhook.py[R131-133]

+        ssl_verify = get_settings().get("GITLAB.SSL_VERIFY", True)
+        auth_method = get_settings().get("GITLAB.AUTH_TYPE", "oauth_token")
+        kwargs = {"url": gitlab_url, "ssl_verify": ssl_verify}
Evidence
Rule 18 requires validating/normalizing configuration inputs at the boundary. The code reads
GITLAB.SSL_VERIFY and directly uses it in kwargs without ensuring it is a supported type/value
(bool or CA path).

pr_agent/servers/gitlab_webhook.py[131-137]
Best Practice: Learned patterns

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

## Issue description
`ssl_verify` is read from settings and forwarded to the GitLab client without boundary validation/normalization.

## Issue Context
Config/env layers often provide booleans as strings; this can silently alter behavior or weaken TLS verification if interpreted unexpectedly.

## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[131-133]

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


11. Unneeded bot-id resolution 🐞 Bug ➹ Performance ⭐ New
Description
The reviewer-assignment path can call is_bot_assigned_as_reviewer() (and thus _get_bot_user_id()
GitLab API auth) even when _perform_commands_gitlab() would later exit early due to
should_process_pr_logic(), causing avoidable latency and GitLab API load on ignored MRs.
Code

pr_agent/servers/gitlab_webhook.py[R336-355]

+                apply_repo_settings(url)
+                handle_assignment = get_settings().gitlab.get("handle_reviewer_assignment", False)
+                if isinstance(handle_assignment, str):
+                    handle_assignment = handle_assignment.lower() in ("true", "1", "yes")
+                if not handle_assignment:
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
+                if is_draft(data):
+                    get_logger().info(f"Skipping draft MR reviewer assignment: {url}")
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
+                if await is_bot_assigned_as_reviewer(data):
+                    reviewer_commands = get_settings().gitlab.get("reviewer_commands", [])
+                    if not isinstance(reviewer_commands, list) or not all(isinstance(c, str) for c in reviewer_commands):
+                        get_logger().warning("gitlab.reviewer_commands is not a list of strings, skipping")
+                        return JSONResponse(status_code=status.HTTP_200_OK,
+                                            content=jsonable_encoder({"message": "success"}))
+                    get_logger().info(f"Bot was assigned as reviewer on MR: {url}")
+                    await _perform_commands_gitlab("reviewer_commands", PRAgent(), url, log_context, data)
+
Evidence
The reviewer-assignment block calls is_bot_assigned_as_reviewer() before invoking
_perform_commands_gitlab(). _perform_commands_gitlab() is where should_process_pr_logic(data)
is applied as an early return, so ignored MRs can still trigger bot-id resolution work first.

pr_agent/servers/gitlab_webhook.py[41-51]
pr_agent/servers/gitlab_webhook.py[323-355]
pr_agent/servers/gitlab_webhook.py[153-170]

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

### Issue description
In the reviewer-assignment handler, the code may perform bot user-id resolution (GitLab API call) before any check that would cause the command execution path to short-circuit due to `should_process_pr_logic(data)`. Since `_perform_commands_gitlab()` enforces `should_process_pr_logic(data)` and returns early, any work done before that check is wasted for ignored MRs.

### Issue Context
`_perform_commands_gitlab()` applies repo settings and then calls `should_process_pr_logic(data)` before reading commands. The reviewer-assignment branch currently calls `is_bot_assigned_as_reviewer(data)` (which calls `_get_bot_user_id()` and can hit the GitLab API) before `_perform_commands_gitlab()` is invoked.

### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[323-355]
- pr_agent/servers/gitlab_webhook.py[41-51]

### Suggested fix
- After `apply_repo_settings(url)` and the feature toggle/draft checks, add `if not should_process_pr_logic(data): return success` *before* calling `is_bot_assigned_as_reviewer(data)`.
- This keeps behavior consistent with `_perform_commands_gitlab()` and avoids the bot-id resolution/API call when the MR would be ignored anyway.

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


View more (10)
12. handle_reviewer_assignment lacks normalization 📘 Rule violation ☼ Reliability
Description
The new reviewer-assignment path uses `get_settings().gitlab.get("handle_reviewer_assignment",
False) as a boolean without validating/normalizing type, so env/config overrides like "false"` can
be treated as truthy and unintentionally enable the feature. This can cause surprising behavior and
makes configuration handling less robust.
Code

pr_agent/servers/gitlab_webhook.py[R330-333]

+                apply_repo_settings(url)
+                if not get_settings().gitlab.get("handle_reviewer_assignment", False):
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
Evidence
PR Compliance ID 19 requires config inputs be validated/normalized at boundaries. The new code
directly uses the config value in a boolean check without any type normalization, which can lead to
incorrect behavior for non-bool inputs.

pr_agent/servers/gitlab_webhook.py[330-333]
Best Practice: Learned patterns

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

## Issue description
`gitlab.handle_reviewer_assignment` is consumed as a boolean without validation/normalization, which can misbehave when provided via env vars or mis-typed config values.
## Issue Context
Dynaconf/env overrides may provide strings (e.g., `"false"`) which are truthy in Python, unintentionally enabling reviewer-assignment triggers.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[330-333]

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


13. url used without guard ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The reviewer-assignment handler reads url = object_attributes.get('url') and then calls
apply_repo_settings(url) without validating that url is present. If the webhook payload is
missing/has a malformed url, this can raise errors or behave unpredictably instead of failing
gracefully.
Code

pr_agent/servers/gitlab_webhook.py[R325-331]

+                url = object_attributes.get('url')
+                # Fast early-exit: no reviewer changes means nothing to do
+                if "reviewers" not in data.get("changes", {}):
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
+                apply_repo_settings(url)
+                if not get_settings().gitlab.get("handle_reviewer_assignment", False):
Evidence
PR Compliance ID 20 requires defensive access and guards for optional/variable external payloads.
The added branch extracts url via .get() and immediately uses it without validation, which is
unsafe if the field is absent or invalid.

pr_agent/servers/gitlab_webhook.py[325-331]
Best Practice: Learned patterns

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

## Issue description
The new reviewer-assignment branch uses `object_attributes.get("url")` and passes it to downstream logic without checking for `None`/empty values.
## Issue Context
Webhook payloads are external inputs and can be variable; defensive checks should prevent unexpected exceptions or confusing logs when required fields are missing.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[325-331]

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


14. TypeError on non-dict changes ✓ Resolved 🐞 Bug ☼ Reliability
Description
In the reviewer-assignment path, the check "reviewers" not in data.get("changes", {}) assumes
changes is dict-like; if GitLab sends changes: null (or any non-dict), this expression raises
TypeError and the background task aborts for that event. This prevents reviewer-assignment
triggering from running and will emit server errors/log noise.
Code

pr_agent/servers/gitlab_webhook.py[R324-329]

+            elif object_attributes.get('action') == 'update' and not object_attributes.get('oldrev'):
+                url = object_attributes.get('url')
+                # Fast early-exit: no reviewer changes means nothing to do
+                if "reviewers" not in data.get("changes", {}):
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
Evidence
The reviewer-assignment early-exit performs a membership test on data.get("changes", {}) without a
type check, which will throw if changes is present but not a dict; in contrast,
is_bot_assigned_as_reviewer() explicitly guards changes with isinstance(changes, dict) before
checking for "reviewers".

pr_agent/servers/gitlab_webhook.py[323-329]
pr_agent/servers/gitlab_webhook.py[153-170]

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

## Issue description
The reviewer-assignment handler uses a membership test on `data.get("changes", {})` without verifying the returned value is a dict. If the payload includes a `changes` key with a non-dict value (e.g., `null`), Python raises `TypeError: argument of type 'NoneType' is not iterable`, aborting the background task for that webhook event.
### Issue Context
There is already a defensive pattern in `is_bot_assigned_as_reviewer()` that checks `changes` is a dict before looking for `"reviewers"`.
### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[323-329]
- pr_agent/servers/gitlab_webhook.py[153-170]
### Proposed fix
Replace the membership check with a type-guarded version, e.g.:

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


15. Repeated repo-settings application 🐞 Bug ➹ Performance
Description
The reviewer-assignment flow applies repo settings before checking handle_reviewer_assignment, and
then calls _perform_commands_gitlab() and PRAgent.handle_request(), both of which also apply
repo settings. This can cause multiple repo-settings fetch/merge passes (and potentially multiple
provider calls) per assignment event, increasing latency and GitLab API load.
Code

pr_agent/servers/gitlab_webhook.py[R330-345]

+                apply_repo_settings(url)
+                if not get_settings().gitlab.get("handle_reviewer_assignment", False):
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
+                if is_draft(data):
+                    get_logger().info(f"Skipping draft MR reviewer assignment: {url}")
+                    return JSONResponse(status_code=status.HTTP_200_OK,
+                                        content=jsonable_encoder({"message": "success"}))
+                if await is_bot_assigned_as_reviewer(data):
+                    reviewer_commands = get_settings().gitlab.get("reviewer_commands", [])
+                    if not isinstance(reviewer_commands, list) or not all(isinstance(c, str) for c in reviewer_commands):
+                        get_logger().warning("gitlab.reviewer_commands is not a list of strings, skipping")
+                        return JSONResponse(status_code=status.HTTP_200_OK,
+                                            content=jsonable_encoder({"message": "success"}))
+                    get_logger().info(f"Bot was assigned as reviewer on MR: {url}")
+                    await _perform_commands_gitlab("reviewer_commands", PRAgent(), url, log_context, data)
Evidence
The new reviewer-assignment branch calls apply_repo_settings(url) and then triggers
_perform_commands_gitlab(...); _perform_commands_gitlab() applies repo settings again, and
PRAgent._handle_request() applies repo settings again. apply_repo_settings() may fetch repo
settings via the provider (git_provider.get_repo_settings()), making the duplication costly.

pr_agent/servers/gitlab_webhook.py[323-345]
pr_agent/servers/gitlab_webhook.py[41-60]
pr_agent/agent/pr_agent.py[50-57]
pr_agent/git_providers/utils.py[14-27]

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

## Issue description
In the reviewer-assignment path, `apply_repo_settings(url)` is invoked and then `_perform_commands_gitlab(...)` is called, which itself invokes `apply_repo_settings(api_url)`. Additionally, `PRAgent._handle_request()` applies repo settings again. This can lead to multiple repo-settings retrieval/merge operations per single webhook event.
### Issue Context
`apply_repo_settings()` can call `git_provider.get_repo_settings()`, which is typically an external provider operation (often a network/API call), so repeated invocations matter.
### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[323-345]
- pr_agent/servers/gitlab_webhook.py[41-60]
- pr_agent/agent/pr_agent.py[50-57]
- pr_agent/git_providers/utils.py[14-27]
### Proposed fix
Refactor to apply repo settings only once per event/command chain. Options:
1) Add a flag to `_perform_commands_gitlab(..., repo_settings_applied: bool = False)` and skip its internal `apply_repo_settings()` when already applied in the caller.
2) Alternatively, create a small internal helper used by both caller and `_perform_commands_gitlab` that ensures repo settings are applied exactly once per request context.
Ensure the reviewer-assignment path can still read repo overrides for `gitlab.handle_reviewer_assignment` before deciding whether to run commands.

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


16. Unsorted hashlib import ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The new hashlib import is separated from the other standard-library imports, which is likely to
violate the repo’s Ruff/isort import-grouping standards. This can create noisy diffs and CI lint
failures.
Code

pr_agent/servers/gitlab_webhook.py[R4-10]

import re
from datetime import datetime
+import hashlib
+
import uvicorn
from fastapi import APIRouter, FastAPI, Request, status
Evidence
PR Compliance ID 6 requires Python changes to conform to Ruff/isort formatting expectations. The
modified import block adds import hashlib but leaves it separated from the other stdlib imports,
which is inconsistent with typical isort grouping/sorting rules.

AGENTS.md
pr_agent/servers/gitlab_webhook.py[1-10]

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

## Issue description
A new stdlib import (`hashlib`) was added in a way that likely violates the repository’s Ruff/isort import grouping/sorting expectations.
## Issue Context
The file’s import section now has a separated stdlib import block, which typical isort settings would rewrite.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[1-10]

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


17. Blocking call in async ✓ Resolved 🐞 Bug ➹ Performance
Description
_get_bot_user_id() performs a synchronous GitLab API authentication call (gl.auth()) from within
the async webhook background task, which can block the event loop/worker while the network call
runs. This can delay processing of concurrent webhook requests on the same worker when
reviewer-assignment triggering is enabled and the cache misses.
Code

pr_agent/servers/gitlab_webhook.py[R128-144]

+    try:
+        import gitlab
+
+        ssl_verify = get_settings().get("GITLAB.SSL_VERIFY", True)
+        auth_method = get_settings().get("GITLAB.AUTH_TYPE", "oauth_token")
+        kwargs = {"url": gitlab_url, "ssl_verify": ssl_verify}
+        if auth_method == "oauth_token":
+            kwargs["oauth_token"] = gitlab_token
+        else:
+            kwargs["private_token"] = gitlab_token
+
+        gl = gitlab.Gitlab(**kwargs)
+        gl.auth()
+        user_id = gl.user.id
+        _bot_user_id_cache[cache_key] = user_id
+        get_logger().info(f"Bot user ID resolved via API: {user_id}")
+        return user_id
Evidence
The webhook schedules an async inner() task (background_tasks.add_task(inner, request_json)),
and the reviewer-assignment path calls is_bot_assigned_as_reviewer(), which calls
_get_bot_user_id() that executes gl.auth() synchronously. This creates a blocking network call
inside an async execution path.

pr_agent/servers/gitlab_webhook.py[115-145]
pr_agent/servers/gitlab_webhook.py[231-352]

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

## Issue description
`_get_bot_user_id()` does blocking I/O (`gitlab.Gitlab(...); gl.auth()`) but is called from the async webhook processing path. This blocks the event loop/worker thread during the API call.
## Issue Context
The GitLab webhook handler schedules `inner()` via Starlette `BackgroundTasks`, but the task still runs on the server’s event loop thread. Any synchronous network I/O inside that task blocks other async work on the same worker.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[115-148]
- pr_agent/servers/gitlab_webhook.py[231-352]
## Suggested fix
- Make bot-id resolution non-blocking by running the GitLab API call in a thread:
- Convert `_get_bot_user_id()` into an async function and call `await asyncio.to_thread(_get_bot_user_id_sync)` (or `run_in_executor`) for the blocking `gl.auth()` portion.
- Or resolve and cache the bot user id during startup (sync init) and reuse it.
- Keep the rest of the reviewer-assignment logic unchanged.

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


18. is_bot_assigned_as_reviewer lacks type guards ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The new reviewer-diff parsing assumes data['changes'] is a dict and that reviewer entries are
dict-like (r.get('id')), which can raise runtime errors on schema variability. Although exceptions
are caught, this can still cause noisy logs and silently skip intended behavior instead of handling
payload variance defensively.
Code

pr_agent/servers/gitlab_webhook.py[R131-143]

+def is_bot_assigned_as_reviewer(data) -> bool:
+    try:
+        if 'reviewers' not in data.get('changes', {}):
+            return False
+        reviewers_change = data['changes']['reviewers']
+        previous = reviewers_change.get('previous', [])
+        current = reviewers_change.get('current', [])
+        bot_user_id = _get_bot_user_id()
+        if bot_user_id is None:
+            return False
+        previous_ids = {r.get('id') for r in previous} if isinstance(previous, list) else set()
+        current_ids = {r.get('id') for r in current} if isinstance(current, list) else set()
+        return bot_user_id in current_ids and bot_user_id not in previous_ids
Evidence
PR Compliance ID 20 requires defensive access patterns for optional/variable payload structures. The
added code directly indexes data['changes']['reviewers'] and calls .get() on list elements
without verifying they are dicts, making it fragile against payload variability.

pr_agent/servers/gitlab_webhook.py[131-146]
Best Practice: Learned patterns

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

## Issue description
`is_bot_assigned_as_reviewer()` relies on assumptions about webhook payload structure (dict/list/dict elements). With external payloads, schema differences can trigger exceptions and skip behavior.
## Issue Context
Compliance requires defensive access patterns for optional/variable external data structures (avoid direct indexing/unsafe method calls without type checks).
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[131-146]

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


19. reviewer_commands not validated ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The new reviewer-assignment path executes commands from gitlab.reviewer_commands without
validating type/contents, so misconfiguration could lead to unexpected iteration/execution behavior.
Configuration inputs should be validated/normalized with targeted warnings or safe defaults.
Code

pr_agent/servers/gitlab_webhook.py[R300-302]

+                if get_settings().gitlab.get('handle_reviewer_assignment', False):
+                    get_logger().info(f"Bot was assigned as reviewer on MR: {url}")
+                    await _perform_commands_gitlab("reviewer_commands", PRAgent(), url, log_context, data)
Evidence
PR Compliance ID 19 requires validating/normalizing configuration inputs and applying safe defaults
with targeted warnings/errors. The new path triggers `_perform_commands_gitlab("reviewer_commands",
...) based on config but does not validate that reviewer_commands` is well-formed before
execution.

pr_agent/servers/gitlab_webhook.py[296-303]
pr_agent/settings/configuration.toml[272-276]
Best Practice: Learned patterns

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

## Issue description
Before executing reviewer-assignment commands, ensure `gitlab.reviewer_commands` is a list of strings (and optionally non-empty), otherwise warn and skip.
## Issue Context
This code path is triggered by external events; configuration errors should not cause surprising behavior.
## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[296-303]
- pr_agent/settings/configuration.toml[272-276]

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


20. Unnecessary API calls ✓ Resolved 🐞 Bug ➹ Performance
Description
The reviewer-assignment code calls is_bot_assigned_as_reviewer (which may hit the GitLab API) before
checking handle_reviewer_assignment, so reviewer changes can trigger network work even when the
featu...

Comment thread pr_agent/servers/gitlab_webhook.py Outdated
Comment on lines +111 to +126
_bot_user_id_cache = None

def _get_bot_user_id():
global _bot_user_id_cache
if _bot_user_id_cache is not None:
return _bot_user_id_cache
try:
import gitlab
gl = gitlab.Gitlab(
get_settings().get("GITLAB.URL", "https://gitlab.com"),
private_token=get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None),
)
gl.auth()
_bot_user_id_cache = gl.user.id
get_logger().info(f"Bot user ID resolved via API: {_bot_user_id_cache}")
return _bot_user_id_cache
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. _bot_user_id_cache shared global state 📘 Rule violation ⛨ Security

_get_bot_user_id() caches a token-derived GitLab user ID in the process-global
_bot_user_id_cache, which can persist across webhook requests and silently apply the wrong
identity when tokens/instances differ. This can cause incorrect authorization/behavior (e.g.,
reviewer-assignment detection misfiring) and cross-request state leakage in multi-tenant,
rotated-token, or warm AWS Lambda deployments.
Agent Prompt
## Issue description
`_bot_user_id_cache` is a module-level cache for a value derived from GitLab credentials (the current user/bot id). In a server handling multiple webhook requests—potentially with different tokens, tenants, or GitLab instances—this shared mutable global can persist across requests (and warm AWS Lambda invocations) and cause incorrect bot identity decisions, including reviewer-assignment detection triggering for the wrong user or failing to trigger.

## Issue Context
The webhook handler can set GitLab credentials dynamically per request (e.g., via secret provider and `X-Gitlab-Token`). The bot user id must be scoped to the effective credentials (and ideally the GitLab URL/instance) rather than stored in a single process-wide variable; otherwise, the cached ID can silently mismatch the current request’s token/instance, violating the intent of PR Compliance ID 18 (avoid shared mutable globals for provider scoping and protect against silent overrides).

## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[111-129]
- pr_agent/servers/gitlab_webhook.py[207-243]
- pr_agent/servers/gitlab_lambda_webhook.py[2-7]

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

Comment on lines +296 to +302
# for reviewer assignment triggered merge requests
elif object_attributes.get('action') == 'update' and is_bot_assigned_as_reviewer(data):
url = object_attributes.get('url')
apply_repo_settings(url)
if get_settings().gitlab.get('handle_reviewer_assignment', False):
get_logger().info(f"Bot was assigned as reviewer on MR: {url}")
await _perform_commands_gitlab("reviewer_commands", PRAgent(), url, log_context, data)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. No tests for reviewer assignment 📘 Rule violation ⚙ Maintainability

The PR adds new behavior to auto-trigger commands when the bot is assigned as a reviewer, but no
corresponding pytest coverage is added/updated. This increases regression risk for webhook parsing
and configuration toggle behavior.
Agent Prompt
## Issue description
Reviewer-assignment-triggered command execution is new behavior and needs tests to prevent regressions.

## Issue Context
Add unit tests to validate:
- detection logic for bot assignment based on `changes.reviewers.previous/current`
- `handle_reviewer_assignment` gating
- correct command list used (`reviewer_commands`)

## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[296-303]

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

Comment thread pr_agent/servers/gitlab_webhook.py Outdated
Comment on lines +118 to +126
import gitlab
gl = gitlab.Gitlab(
get_settings().get("GITLAB.URL", "https://gitlab.com"),
private_token=get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None),
)
gl.auth()
_bot_user_id_cache = gl.user.id
get_logger().info(f"Bot user ID resolved via API: {_bot_user_id_cache}")
return _bot_user_id_cache
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

3. Auth type ignored 🐞 Bug ≡ Correctness

_get_bot_user_id always authenticates using private_token and does not pass SSL verification
settings, while the rest of the GitLab integration supports oauth_token/private_token selection via
GITLAB.AUTH_TYPE and SSL_VERIFY. This can prevent bot user-id resolution in oauth_token deployments
and silently disable reviewer-assignment triggering.
Agent Prompt
### Issue description
`_get_bot_user_id()` hardcodes `private_token=...` and omits `ssl_verify`, diverging from `GitLabProvider` which selects auth based on `GITLAB.AUTH_TYPE` and configures SSL verification.

### Issue Context
If `GITLAB.AUTH_TYPE` is set to (or defaults to) oauth token usage, the hardcoded `private_token` path can fail and `is_bot_assigned_as_reviewer()` will always return `False`.

### Fix
- Mirror the `GitLabProvider` client creation logic:
  - read `GITLAB.AUTH_TYPE` and pass either `oauth_token=` or `private_token=` accordingly
  - pass `ssl_verify=get_settings().get("GITLAB.SSL_VERIFY", True)`
- Prefer extracting a shared helper to create an authenticated python-gitlab client to avoid drift.

### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[113-126]
- pr_agent/git_providers/gitlab_provider.py[33-65]

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

@igoraj igoraj force-pushed the feature/gitlab-reviewer-assignment-trigger branch from 133a196 to dfe13bf Compare May 12, 2026 09:28
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit dfe13bf

Comment thread pr_agent/servers/gitlab_webhook.py Outdated
Comment on lines +150 to +167
def is_bot_assigned_as_reviewer(data) -> bool:
try:
changes = data.get('changes')
if not isinstance(changes, dict):
return False
if 'reviewers' not in changes:
return False
reviewers_change = changes['reviewers']
if not isinstance(reviewers_change, dict):
return False
previous = reviewers_change.get('previous', [])
current = reviewers_change.get('current', [])
bot_user_id = _get_bot_user_id()
if bot_user_id is None:
return False
previous_ids = {r.get('id') for r in previous if isinstance(r, dict)}
current_ids = {r.get('id') for r in current if isinstance(r, dict)}
return bot_user_id in current_ids and bot_user_id not in previous_ids
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. New strings use single quotes 📘 Rule violation ⚙ Maintainability

Newly added code uses single-quoted strings (e.g., dict keys like data.get('changes')), which
conflicts with the compliance requirement to use double quotes for strings. This can cause avoidable
Ruff formatting/lint failures.
Agent Prompt
## Issue description
New code uses single-quoted strings, but the compliance checklist requires double quotes for Python strings.

## Issue Context
This affects newly added GitLab reviewer-assignment logic and may trigger Ruff formatting/lint failures.

## Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[150-167]
- pr_agent/servers/gitlab_webhook.py[320-333]

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

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit efbbf0d

Comment on lines +4 to +6
os.environ.setdefault("GITLAB__URL", "https://gitlab.example.com")
import pr_agent.servers.gitlab_webhook as gitlab_webhook

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Late import after env 📘 Rule violation ⚙ Maintainability

The test sets GITLAB__URL at module import time and then performs a module-level import of
pr_agent.servers.gitlab_webhook, triggering Ruff E402 and making test behavior depend on ambient
environment state. This violates lint/style conventions and the requirement that tests explicitly
control environment variables/process-global state for determinism.
Agent Prompt
## Issue description
`tests/unittest/test_gitlab_reviewer_assignment.py` executes `os.environ.setdefault(...)` at import time and then imports `pr_agent.servers.gitlab_webhook` at module scope, which triggers Ruff `E402` and makes the test rely on implicit/ambient environment state.

## Issue Context
`pr_agent.servers.gitlab_webhook` performs work at import time (e.g., logger setup using `get_settings()`), so tests that need env/config should set env vars explicitly inside a fixture/test, then import/reload the module deterministically.

## Fix Focus Areas
- tests/unittest/test_gitlab_reviewer_assignment.py[1-6]

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

Comment thread tests/unittest/test_gitlab_reviewer_assignment.py Outdated
Comment thread pr_agent/servers/gitlab_webhook.py
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit b00b975

…b MR

GitLab provides built-in commands like /request_review and /assign_reviewer
which currently do not trigger the PR-Agent. This PR adds webhook handling
for reviewer assignment events to automatically trigger commands (default: /review).

Features:
- Adds handle_reviewer_assignment toggle under [gitlab] section (default: false)
- Supports configurable reviewer_commands array (default: ['/review'])
- Bot identifies itself dynamically using the GitLab API
- Safely skips draft MRs and invalid events
- Validates configurations and payload structures to avoid exceptions

Assisted-by: opencode:deepseek-v4-pro
@igoraj igoraj force-pushed the feature/gitlab-reviewer-assignment-trigger branch from b00b975 to 779a192 Compare May 12, 2026 11:02
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 779a192

Comment on lines +122 to +151
cache_key = hashlib.sha256(f"{gitlab_url}:{gitlab_token}".encode()).hexdigest()

cached = _bot_user_id_cache.get(cache_key)
if cached is not None:
return cached if cached != -1 else None

def _resolve_sync():
import gitlab

ssl_verify = get_settings().get("GITLAB.SSL_VERIFY", True)
auth_method = get_settings().get("GITLAB.AUTH_TYPE", "oauth_token")
kwargs = {"url": gitlab_url, "ssl_verify": ssl_verify}
if auth_method == "oauth_token":
kwargs["oauth_token"] = gitlab_token
else:
kwargs["private_token"] = gitlab_token

gl = gitlab.Gitlab(**kwargs)
gl.auth()
return gl.user.id

try:
user_id = await asyncio.to_thread(_resolve_sync)
_bot_user_id_cache[cache_key] = user_id
get_logger().info(f"Bot user ID resolved via API: {user_id}")
return user_id
except Exception as e:
_bot_user_id_cache[cache_key] = -1
get_logger().error(f"Failed to resolve bot user ID: {e}")
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Permanent failure cache 🐞 Bug ☼ Reliability

_get_bot_user_id() caches a sentinel (-1) on any exception and then returns None for all future
calls for that URL/token, so one transient GitLab auth/network failure can permanently disable
reviewer-assignment triggering until process restart.
Agent Prompt
### Issue description
`_get_bot_user_id()` stores `-1` in `_bot_user_id_cache` on *any* exception, and subsequent calls treat that as a permanent failure (returning `None`). This makes reviewer-assignment triggering brittle: a transient outage/rate-limit/timeout can disable the feature until the service is restarted.

### Issue Context
The cache is process-global and has no expiration logic. The code currently uses `-1` as a negative-cache sentinel with no retry window.

### Fix Focus Areas
- pr_agent/servers/gitlab_webhook.py[113-151]

### Suggested fix
- Replace the `-1` sentinel with a small structure like `{value, ts, is_failure}` and only honor negative-cache entries for a short TTL (e.g., 30–300 seconds), after which a retry is allowed.
- Alternatively, do not cache failures at all (or cache only specific non-retryable failures), and keep caching only successful resolutions.
- Add/adjust a unit test to verify retries occur after TTL expiry (or that failures are not cached).

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant