Skip to content

fix: use get_app().slug for app_bot_login instead of get_user().login#1114

Open
rnetser wants to merge 6 commits into
mainfrom
fix/issue-1113-app-bot-login
Open

fix: use get_app().slug for app_bot_login instead of get_user().login#1114
rnetser wants to merge 6 commits into
mainfrom
fix/issue-1113-app-bot-login

Conversation

@rnetser

@rnetser rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

app_bot_login initialization fails on every webhook with a 403 error:

Failed to get app bot login — bot-PR detection may not work

get_user() always returns 403 for GitHub App installation tokens — this is a GitHub platform limitation, not a permissions issue.

Impact

Without app_bot_login, the server cannot identify its own PRs:

  • Cherry-pick retry cannot verify bot ownership of existing PRs
  • Rebase auth degradation — falls back to less reliable detection

Fix

Replace get_user().login with get_app() + f"{slug}[bot]". The get_app() endpoint is designed for app tokens and returns app metadata including slug. The bot login format is always {slug}[bot].

Closes #1113

Generated-by: Claude noreply@anthropic.com

get_user() always returns 403 for GitHub App installation tokens —
it's a platform limitation, not a permissions issue. get_app() is
the correct endpoint for app tokens and returns app metadata
including slug. Bot login format is always {slug}[bot].

Closes #1113

Signed-off-by: rnetser <rnetser@redhat.com>
Generated-by: Claude <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used

Grey Divider


Action required

1. get_github_app_slug() swallows exceptions ✓ Resolved 📘 Rule violation ☼ Reliability
Description
get_github_app_slug() catches all exceptions and returns None, which prevents
github_api_call() from observing transient GitHub/API/network failures and applying its
retry/backoff logic. As a result, process() may only log an error and leave app_bot_login unset,
causing silent initialization failures and degraded bot-PR detection until a later webhook succeeds.
Code

webhook_server/utils/github_repository_settings.py[R440-451]

+    try:
+        with open(os.path.join(config_.data_dir, "webhook-server.private-key.pem")) as fd:
+            private_key = fd.read()
+
+        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)
+        return app_instance.get_app().slug
+
+    except Exception:
+        LOGGER.exception("Failed to get GitHub App slug")
+        return None
Evidence
PR Compliance ID 8 requires PyGithub operations to run through github_api_call() so transient
failures can be retried with backoff, but the new get_github_app_slug() wraps access to
app_instance.get_app().slug in a broad exception handler that converts any exception into None.
Since github_api_call() retries only when exceptions propagate out of the callable it wraps,
swallowing exceptions in the helper disables retries for this initialization path even when the
underlying error is retryable (e.g., 500/502/503/504 or other network/API failures).

CLAUDE.md: Wrap All Potentially Blocking PyGithub Operations with github_api_call() (No Direct PyGithub Calls)
webhook_server/utils/github_repository_settings.py[440-451]
webhook_server/utils/github_repository_settings.py[435-451]
webhook_server/libs/github_api.py[544-558]
webhook_server/utils/github_retry.py[65-126]

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_github_app_slug()` currently wraps its implementation in a broad `try/except Exception` and returns `None` on any failure, which prevents `github_api_call()` from retrying transient GitHub/API/network errors because no exception propagates. This can leave `app_bot_login` unset (after `process()` logs an error), degrading bot-PR detection until a later webhook run succeeds.

## Issue Context
- PR Compliance ID 8 expects PyGithub operations to be executed via `github_api_call()` to get retry/backoff behavior.
- `webhook_server/libs/github_api.py` uses `github_api_call()` to run work off the event loop and to retry transient failures.
- `GithubWebhook.process()` initializes `app_bot_login` via `await github_api_call(get_github_app_slug, ...)`, but the helper’s broad exception handler converts failures from `app_instance.get_app().slug` into `None`, defeating retry/backoff.

## Fix Focus Areas
- webhook_server/utils/github_repository_settings.py[435-451]
- webhook_server/libs/github_api.py[544-558]

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


2. No tests for app_bot_login 📎 Requirement gap ☼ Reliability
Description
app_bot_login initialization behavior was changed to derive the login from get_app().slug, but
there are no accompanying tests validating this initialization path for GitHub App installation
tokens. This risks regressions where app_bot_login becomes unset/incorrect and bot-PR detection
breaks silently.
Code

webhook_server/libs/github_api.py[R554-559]

+                    _app = await github_api_call(
+                        _github_app_api.get_app,
                        logger=self.logger,
                        log_prefix=self.log_prefix,
                    )
+                    self.app_bot_login = f"{_app.slug}[bot]"
Evidence
Rule IDs 2 and 9 require updating/adding automated tests for the new app_bot_login initialization
behavior. The PR changes GithubWebhook.process() to set `self.app_bot_login =
f"{_app.slug}[bot]", but current tests shown rely on manually setting app_bot_login` rather than
validating initialization, leaving the new get_app().slug construction untested.

Update and pass tests covering app bot login initialization for installation tokens
CLAUDE.md: Maintain at least 90% test coverage and add tests for new code
webhook_server/libs/github_api.py[543-565]
webhook_server/tests/test_issue_comment_handler.py[2075-2084]
webhook_server/tests/test_runner_handler.py[2891-2906]

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

## Issue description
`app_bot_login` initialization was changed to use `get_app().slug` + `[bot]`, but there are no tests asserting this behavior (and existing tests often set `app_bot_login` manually).

## Issue Context
Compliance requires tests that validate `app_bot_login` initializes successfully under GitHub App installation token auth using `GET /app`, and that the constructed value matches `{slug}[bot]`.

## Fix Focus Areas
- webhook_server/libs/github_api.py[543-565]
- webhook_server/tests/test_github_api.py[237-305]
- webhook_server/tests/test_issue_comment_handler.py[2075-2084]
- webhook_server/tests/test_runner_handler.py[2891-2906]

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



Remediation recommended

3. Unscoped slug cache 🐞 Bug ≡ Correctness ⭐ New
Description
get_github_app_slug() caches a single slug in a module-global variable without keying by Config
(github-app-id / data_dir), so a later call with a different Config can return the wrong slug and
generate an incorrect app_bot_login.
Code

webhook_server/utils/github_repository_settings.py[R443-446]

+    global _github_app_slug_cache
+
+    if _github_app_slug_cache is not None:
+        return _github_app_slug_cache
Evidence
The implementation returns a single cached slug regardless of which Config is passed, even though
the slug is derived from config-provided app id and key path; Config instances can legitimately
differ because data_dir is environment-derived and app id is read from config.yaml (which may vary
across configs/repositories/tests).

webhook_server/utils/github_repository_settings.py[45-46]
webhook_server/utils/github_repository_settings.py[436-456]
webhook_server/libs/config.py[13-22]
webhook_server/libs/github_api.py[862-866]

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_github_app_slug()` stores a single process-wide `_github_app_slug_cache` and returns it for all calls once set. If the same process ever uses multiple `Config` instances with different `github-app-id` and/or different `data_dir` (different private key/config), the cached slug can be wrong, producing an incorrect `{slug}[bot]` login.

## Issue Context
The function already takes `config_` and reads both `config_.data_dir` and `config_.root_data["github-app-id"]`, which implies the returned value is config-dependent. A process-wide singleton cache breaks that implied contract.

## Fix Focus Areas
- webhook_server/utils/github_repository_settings.py[46-46]
- webhook_server/utils/github_repository_settings.py[443-447]
- webhook_server/utils/github_repository_settings.py[454-455]

## Suggested fix
- Change `_github_app_slug_cache` from `str | None` to a dict keyed by something like `(github_app_id, config_.data_dir)` (or `(github_app_id, config_.config_path)`), and cache per-key.
- Alternatively, refactor into a helper `@functools.lru_cache` function that accepts hashable inputs (e.g., `app_id: int, pem_path: str`) and have `get_github_app_slug(config_)` call it.
- Ensure behavior remains: raise on failure so `github_api_call()` can apply retry/backoff.

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


4. Uncached GitHub App slug ✓ Resolved 🐞 Bug ➹ Performance
Description
get_github_app_slug() reads the private key and calls GitHub (GET /app) every time it’s invoked,
and GithubWebhook.process() invokes it per webhook because app_bot_login is reset on each new
handler instance. This adds avoidable per-event I/O/API latency and can amplify transient failures
into repeated error logs under high webhook volume.
Code

webhook_server/utils/github_repository_settings.py[R441-447]

+    with open(os.path.join(config_.data_dir, "webhook-server.private-key.pem")) as fd:
+        private_key = fd.read()
+
+    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)
+    return app_instance.get_app().slug
Evidence
A new webhook handler instance is created for each request, and it resets app_bot_login to an
empty string, which causes process() to call get_github_app_slug() on every webhook. The helper
function performs both disk I/O (PEM read) and a GitHub API call (get_app()) each time, so the
overhead repeats per event.

webhook_server/app.py[455-461]
webhook_server/libs/github_api.py[200-200]
webhook_server/libs/github_api.py[545-553]
webhook_server/utils/github_repository_settings.py[435-447]

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_github_app_slug()` performs disk I/O (reads the PEM) and a network call (`GithubIntegration(...).get_app().slug`) every time it’s called. Because a new `GithubWebhook` object is created per incoming webhook and `app_bot_login` starts empty, this slug lookup runs on every webhook.

## Issue Context
- `GithubWebhook` is instantiated per request.
- `self.app_bot_login` is initialized to `""` in `__init__`, so `process()` always triggers the slug lookup path.

## Fix Focus Areas
- webhook_server/utils/github_repository_settings.py[435-447]
- webhook_server/libs/github_api.py[545-553]

## Suggested fix
- Cache the app slug (or the derived bot login string) at process-level scope (module global) or application scope:
 - Option A: Introduce a module-level cache (e.g., `_APP_SLUG: str | None`) + a lock to ensure only one fetch occurs concurrently.
 - Option B: Store the slug in `Config` (or another long-lived singleton) after the first successful fetch.
- Ensure cache invalidation is either unnecessary (slug is effectively immutable) or explicitly tied to config changes (e.g., app id / data_dir).

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


5. Slug read outside retry ✓ Resolved 🐞 Bug ☼ Reliability
Description
process() calls github_api_call(_github_app_api.get_app) but then reads _app.slug outside the
wrapper; if slug is lazily loaded by PyGithub, it can perform synchronous HTTP on the event loop
thread and bypass github_api_call retries/logging. Keep the entire bot-login derivation inside
github_api_call (e.g., return the final string from the wrapped callable).
Code

webhook_server/libs/github_api.py[R554-559]

+                    _app = await github_api_call(
+                        _github_app_api.get_app,
                        logger=self.logger,
                        log_prefix=self.log_prefix,
                    )
+                    self.app_bot_login = f"{_app.slug}[bot]"
Evidence
The project’s retry wrapper explicitly calls out using lambdas for property access because PyGithub
is synchronous, and other parts of the code consistently wrap attribute reads
(pull_request.number, pull_request.user.login) with github_api_call for that reason. The new
code returns an App object from the wrapper but then reads .slug outside of it, which breaks that
pattern and can bypass the wrapper’s thread offload/retry if .slug triggers lazy loading.

webhook_server/libs/github_api.py[543-565]
webhook_server/utils/github_retry.py[1-27]
webhook_server/utils/github_retry.py[65-104]
webhook_server/libs/github_api.py[622-630]
webhook_server/libs/handlers/issue_comment_handler.py[752-756]

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

### Issue description
`_app.slug` is accessed outside `github_api_call`, which is the project’s wrapper intended to keep all PyGithub (sync) operations off the event loop and apply retry behavior.

### Issue Context
This repo documents using `github_api_call` for both method calls and property access because PyGithub is synchronous and properties may require blocking work.

### Fix Focus Areas
- webhook_server/libs/github_api.py[543-565]

### Suggested change
Refactor to compute the final bot login inside `github_api_call`, e.g.:

```py
self.app_bot_login = await github_api_call(
   lambda: f"{_github_app_api.get_app().slug}[bot]",
   logger=self.logger,
   log_prefix=self.log_prefix,
)
```

(Or fetch `slug` via `github_api_call(lambda: _github_app_api.get_app().slug, ...)` and then format the string.)

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


Grey Divider

Previous review results

Review updated until commit 170ddd7

Results up to commit ba74574


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Action required
1. No tests for app_bot_login 📎 Requirement gap ☼ Reliability
Description
app_bot_login initialization behavior was changed to derive the login from get_app().slug, but
there are no accompanying tests validating this initialization path for GitHub App installation
tokens. This risks regressions where app_bot_login becomes unset/incorrect and bot-PR detection
breaks silently.
Code

webhook_server/libs/github_api.py[R554-559]

+                    _app = await github_api_call(
+                        _github_app_api.get_app,
                        logger=self.logger,
                        log_prefix=self.log_prefix,
                    )
+                    self.app_bot_login = f"{_app.slug}[bot]"
Evidence
Rule IDs 2 and 9 require updating/adding automated tests for the new app_bot_login initialization
behavior. The PR changes GithubWebhook.process() to set `self.app_bot_login =
f"{_app.slug}[bot]", but current tests shown rely on manually setting app_bot_login` rather than
validating initialization, leaving the new get_app().slug construction untested.

Update and pass tests covering app bot login initialization for installation tokens
CLAUDE.md: Maintain at least 90% test coverage and add tests for new code
webhook_server/libs/github_api.py[543-565]
webhook_server/tests/test_issue_comment_handler.py[2075-2084]
webhook_server/tests/test_runner_handler.py[2891-2906]

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

## Issue description
`app_bot_login` initialization was changed to use `get_app().slug` + `[bot]`, but there are no tests asserting this behavior (and existing tests often set `app_bot_login` manually).

## Issue Context
Compliance requires tests that validate `app_bot_login` initializes successfully under GitHub App installation token auth using `GET /app`, and that the constructed value matches `{slug}[bot]`.

## Fix Focus Areas
- webhook_server/libs/github_api.py[543-565]
- webhook_server/tests/test_github_api.py[237-305]
- webhook_server/tests/test_issue_comment_handler.py[2075-2084]
- webhook_server/tests/test_runner_handler.py[2891-2906]

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



Remediation recommended
2. Slug read outside retry ✓ Resolved 🐞 Bug ☼ Reliability
Description
process() calls github_api_call(_github_app_api.get_app) but then reads _app.slug outside the
wrapper; if slug is lazily loaded by PyGithub, it can perform synchronous HTTP on the event loop
thread and bypass github_api_call retries/logging. Keep the entire bot-login derivation inside
github_api_call (e.g., return the final string from the wrapped callable).
Code

webhook_server/libs/github_api.py[R554-559]

+                    _app = await github_api_call(
+                        _github_app_api.get_app,
                        logger=self.logger,
                        log_prefix=self.log_prefix,
                    )
+                    self.app_bot_login = f"{_app.slug}[bot]"
Evidence
The project’s retry wrapper explicitly calls out using lambdas for property access because PyGithub
is synchronous, and other parts of the code consistently wrap attribute reads
(pull_request.number, pull_request.user.login) with github_api_call for that reason. The new
code returns an App object from the wrapper but then reads .slug outside of it, which breaks that
pattern and can bypass the wrapper’s thread offload/retry if .slug triggers lazy loading.

webhook_server/libs/github_api.py[543-565]
webhook_server/utils/github_retry.py[1-27]
webhook_server/utils/github_retry.py[65-104]
webhook_server/libs/github_api.py[622-630]
webhook_server/libs/handlers/issue_comment_handler.py[752-756]

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

### Issue description
`_app.slug` is accessed outside `github_api_call`, which is the project’s wrapper intended to keep all PyGithub (sync) operations off the event loop and apply retry behavior.

### Issue Context
This repo documents using `github_api_call` for both method calls and property access because PyGithub is synchronous and properties may require blocking work.

### Fix Focus Areas
- webhook_server/libs/github_api.py[543-565]

### Suggested change
Refactor to compute the final bot login inside `github_api_call`, e.g.:

```py
self.app_bot_login = await github_api_call(
   lambda: f"{_github_app_api.get_app().slug}[bot]",
   logger=self.logger,
   log_prefix=self.log_prefix,
)
```

(Or fetch `slug` via `github_api_call(lambda: _github_app_api.get_app().slug, ...)` and then format the string.)

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


Results up to commit a203182


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Action required
1. get_github_app_slug() swallows exceptions ✓ Resolved 📘 Rule violation ☼ Reliability
Description
get_github_app_slug() catches all exceptions and returns None, which prevents
github_api_call() from observing transient GitHub/API/network failures and applying its
retry/backoff logic. As a result, process() may only log an error and leave app_bot_login unset,
causing silent initialization failures and degraded bot-PR detection until a later webhook succeeds.
Code

webhook_server/utils/github_repository_settings.py[R440-451]

+    try:
+        with open(os.path.join(config_.data_dir, "webhook-server.private-key.pem")) as fd:
+            private_key = fd.read()
+
+        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)
+        return app_instance.get_app().slug
+
+    except Exception:
+        LOGGER.exception("Failed to get GitHub App slug")
+        return None
Evidence
PR Compliance ID 8 requires PyGithub operations to run through github_api_call() so transient
failures can be retried with backoff, but the new get_github_app_slug() wraps access to
app_instance.get_app().slug in a broad exception handler that converts any exception into None.
Since github_api_call() retries only when exceptions propagate out of the callable it wraps,
swallowing exceptions in the helper disables retries for this initialization path even when the
underlying error is retryable (e.g., 500/502/503/504 or other network/API failures).

CLAUDE.md: Wrap All Potentially Blocking PyGithub Operations with github_api_call() (No Direct PyGithub Calls)
webhook_server/utils/github_repository_settings.py[440-451]
webhook_server/utils/github_repository_settings.py[435-451]
webhook_server/libs/github_api.py[544-558]
webhook_server/utils/github_retry.py[65-126]

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_github_app_slug()` currently wraps its implementation in a broad `try/except Exception` and returns `None` on any failure, which prevents `github_api_call()` from retrying transient GitHub/API/network errors because no exception propagates. This can leave `app_bot_login` unset (after `process()` logs an error), degrading bot-PR detection until a later webhook run succeeds.

## Issue Context
- PR Compliance ID 8 expects PyGithub operations to be executed via `github_api_call()` to get retry/backoff behavior.
- `webhook_server/libs/github_api.py` uses `github_api_call()` to run work off the event loop and to retry transient failures.
- `GithubWebhook.process()` initializes `app_bot_login` via `await github_api_call(get_github_app_slug, ...)`, but the helper’s broad exception handler converts failures from `app_instance.get_app().slug` into `None`, defeating retry/backoff.

## Fix Focus Areas
- webhook_server/utils/github_repository_settings.py[435-451]
- webhook_server/libs/github_api.py[544-558]

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


Results up to commit 0a38fb1


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Remediation recommended
1. Uncached GitHub App slug ✓ Resolved 🐞 Bug ➹ Performance
Description
get_github_app_slug() reads the private key and calls GitHub (GET /app) every time it’s invoked,
and GithubWebhook.process() invokes it per webhook because app_bot_login is reset on each new
handler instance. This adds avoidable per-event I/O/API latency and can amplify transient failures
into repeated error logs under high webhook volume.
Code

webhook_server/utils/github_repository_settings.py[R441-447]

+    with open(os.path.join(config_.data_dir, "webhook-server.private-key.pem")) as fd:
+        private_key = fd.read()
+
+    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)
+    return app_instance.get_app().slug
Evidence
A new webhook handler instance is created for each request, and it resets app_bot_login to an
empty string, which causes process() to call get_github_app_slug() on every webhook. The helper
function performs both disk I/O (PEM read) and a GitHub API call (get_app()) each time, so the
overhead repeats per event.

webhook_server/app.py[455-461]
webhook_server/libs/github_api.py[200-200]
webhook_server/libs/github_api.py[545-553]
webhook_server/utils/github_repository_settings.py[435-447]

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_github_app_slug()` performs disk I/O (reads the PEM) and a network call (`GithubIntegration(...).get_app().slug`) every time it’s called. Because a new `GithubWebhook` object is created per incoming webhook and `app_bot_login` starts empty, this slug lookup runs on every webhook.

## Issue Context
- `GithubWebhook` is instantiated per request.
- `self.app_bot_login` is initialized to `""` in `__init__`, so `process()` always triggers the slug lookup path.

## Fix Focus Areas
- webhook_server/utils/github_repository_settings.py[435-447]
- webhook_server/libs/github_api.py[545-553]

## Suggested fix
- Cache the app slug (or the derived bot login string) at process-level scope (module global) or application scope:
 - Option A: Introduce a module-level cache (e.g., `_APP_SLUG: str | None`) + a lock to ensure only one fetch occurs concurrently.
 - Option B: Store the slug in `Config` (or another long-lived singleton) after the first successful fetch.
- Ensure cache invalidation is either unnecessary (slug is effectively immutable) or explicitly tied to config changes (e.g., app id / data_dir).

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


Qodo Logo

@myakove-bot

Copy link
Copy Markdown
Collaborator

Report bugs in Issues

Welcome! 🎉

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

🔄 Automatic Actions

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

📋 Available Commands

PR Status Management

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

Review & Approval

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

Testing & Validation

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

Container Operations

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

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3
  • /cherry-pick-retry <branch> - Retry a failed cherry-pick (merged PRs only)

Branch Management

  • /rebase - Rebase this PR branch onto its base branch

Label Management

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

✅ Merge Requirements

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

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 1 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No wip, hold, has-conflicts labels and PR must be mergeable (no conflicts)
  5. Verified: PR must be marked as verified

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge
AI Features
  • Conventional Title: Mode: fix (claude/claude-opus-4-6[1m])
  • Cherry-Pick Conflict Resolution: Enabled (claude/claude-opus-4-6[1m])
  • Test Oracle: Triggers: approved (claude/claude-opus-4-6[1m]); /test-oracle can be used anytime
Security Checks
  • Suspicious Path Detection: Monitors paths: .claude/, .vscode/, .cursor/, .devcontainer/, .pi/, .github/workflows/, .github/actions/
  • Committer Identity Check: Verifies last committer matches PR author
  • Mandatory: Security checks block merge (use /security-override to bypass — maintainers only)

💡 Tips

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

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix app bot login detection using GitHub App slug (avoid get_user() 403)
🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

Walkthroughs

Description
• Fix app_bot_login initialization for GitHub App installation tokens (avoid 403 from get_user()).
• Derive bot login from get_app().slug using the stable {slug}[bot] format.
• Restore reliable bot-owned PR detection for webhook-driven operations (rebase/cherry-pick retry).
Diagram
graph TD
  A["Webhook processor"] --> B["GitHub App API client"] --> C{{"GitHub REST API"}} --> D["App metadata (slug)"] --> E["app_bot_login: {slug}[bot]"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Make bot login configurable (env/setting)
  • ➕ Avoids any GitHub API call during initialization
  • ➕ Works even if GitHub API is partially unavailable
  • ➖ Operationally brittle (must match app slug exactly)
  • ➖ Harder to deploy across multiple environments/apps
2. Attempt get_user() then fall back to get_app()
  • ➕ Potentially preserves behavior for non-app tokens in shared code paths
  • ➖ Extra API call and noisy failures for installation tokens (known 403 behavior)
  • ➖ More complex control flow without adding real value for the target scenario

Recommendation: Keep the PR’s approach: using get_app() is the correct endpoint for GitHub App installation tokens, and deriving {slug}[bot] is stable and deterministic. Configuration-based approaches add operational risk, and get_user() fallback logic would introduce avoidable errors/noise for the primary failing case.

Grey Divider

File Changes

Bug fix (1)
github_api.py Derive app_bot_login from get_app().slug instead of get_user().login +3/-2

Derive app_bot_login from get_app().slug instead of get_user().login

• Replaces the app bot login initialization call that previously used get_user().login (which 403s for installation tokens). The code now calls get_app() to retrieve the app slug and constructs the bot login as '{slug}[bot]'.

webhook_server/libs/github_api.py


Grey Divider

Qodo Logo

Comment thread webhook_server/libs/github_api.py Outdated
Comment thread webhook_server/libs/github_api.py Outdated
@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/github_api.py:554 (qodo requirement gap) — No tests for app_bot_login

Skipped: Skipped — testing process() init path requires mocking the entire webhook flow. The {slug}[bot] format is trivial string formatting. Existing tests set app_bot_login directly which validates downstream usage.

webhook_server/libs/github_api.py:554 (qodo bug) — Slug read outside retry

Addressed: Fixed — wrapped entire operation in a single github_api_call lambda: lambda: f"{_github_app_api.get_app().slug}[bot]". Both get_app() and .slug access now run in the thread pool with retry.

Moves .slug property access inside the github_api_call lambda to
keep it off the event loop and get retry protection, per AGENTS.md
guidelines.

Signed-off-by: rnetser <rnetser@redhat.com>
Assisted-by: Claude <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a5fb576

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

/build-and-push-container

@myakove-bot

Copy link
Copy Markdown
Collaborator

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

get_app() requires AppAuth (JWT), not AppInstallationAuth. Added
get_github_app_slug() helper that creates its own GithubIntegration
with proper AppAuth to call get_app().slug. Removed the intermediate
_github_app_api variable since we no longer need the installation
token for bot login detection.

Signed-off-by: rnetser <rnetser@redhat.com>
Assisted-by: Claude <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a203182

Comment thread webhook_server/utils/github_repository_settings.py Outdated
@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/utils/github_repository_settings.py:440 (qodo rule violation) — get_github_app_slug() swallows exceptions

Addressed: Fixed — removed try/except from get_github_app_slug(). Exceptions now propagate through github_api_call() for retry/backoff, then caught by the outer try/except in process().

webhook_server/libs/github_api.py:554 (qodo requirement gap) — No tests for app_bot_login

Skipped: Skipped — testing process() init path requires mocking the entire webhook flow. Existing tests set app_bot_login directly which validates downstream usage.

Removed try/except from get_github_app_slug() so exceptions propagate
through github_api_call() for retry/backoff on transient failures.
The outer try/except in process() handles permanent failures.

Signed-off-by: rnetser <rnetser@redhat.com>
Assisted-by: Claude <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0a38fb1

Comment thread webhook_server/utils/github_repository_settings.py Outdated
Signed-off-by: rnetser <rnetser@redhat.com>
Assisted-by: Claude <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6e2bd74

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/github_api.py:554 (qodo requirement gap) — No tests for app_bot_login

Skipped: Skipped — testing process() init path requires mocking the entire webhook flow.

webhook_server/utils/github_repository_settings.py:441 (qodo bug) — Uncached GitHub App slug

Addressed: Fixed — added module-level _github_app_slug_cache. First call fetches and caches, subsequent calls return cached value. Slug is immutable so no invalidation needed.

Slug is immutable — no need to read PEM + call GET /app on every
webhook event. First call fetches and caches, subsequent calls
return cached value.

Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: rnetser <rnetser@redhat.com>
@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 170ddd7

Comment thread webhook_server/utils/github_repository_settings.py
@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/github_api.py:554 (qodo requirement gap) — No tests for app_bot_login

Skipped: Skipped — testing process() init path requires mocking the entire webhook flow.

webhook_server/utils/github_repository_settings.py:443 (qodo bug) — Unscoped slug cache

Skipped: Skipped — this is a single-app deployment with one github-app-id. Multiple Config instances all share the same app identity. Keying by config would add complexity for a scenario that does not occur in production.

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/github_api.py:554 (qodo requirement gap) — No tests for app_bot_login

Skipped: Skipped — already addressed in previous cycle.

webhook_server/utils/github_repository_settings.py:443 (qodo bug) — Unscoped slug cache

Skipped: Skipped — single-app deployment, already addressed in previous cycle.

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

/build-and-push-container

@myakove-bot

Copy link
Copy Markdown
Collaborator

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

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

/verified

@rnetser

rnetser commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review re-review

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: app_bot_login fails to initialize — GET /user not available to GitHub App installation tokens

2 participants