Skip to content

feat: cherry-pick retry/rebase commands + pre-commit auto-fix + security hardening#1108

Open
myakove wants to merge 11 commits into
mainfrom
fix/issue-1103-1089-cherry-pick
Open

feat: cherry-pick retry/rebase commands + pre-commit auto-fix + security hardening#1108
myakove wants to merge 11 commits into
mainfrom
fix/issue-1103-1089-cherry-pick

Conversation

@myakove

@myakove myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

PR Summary by Qodo

Add cherry-pick retry + PR rebase commands, pre-commit auto-fix, and command guards
✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

Walkthroughs

User Description

Summary

New commands (#1103)

  • /cherry-pick-retry <branch> — Retry a failed cherry-pick on merged PRs
  • /rebase — Rebase any open PR onto its base branch (with bot-PR ownership validation)

Pre-commit auto-fix (#1089)

  • Run pre-commit after cherry-pick, before push
  • If files modified, commit fixes automatically

Security hardening

  • Add is_user_valid_to_run_commands guard to all 7 previously unguarded commands: /cherry-pick, /assign-reviewer, /assign-reviewers, /check-can-merge, /verified, /wip, /test-oracle
  • Verify bot ownership before closing cherry-pick PR on retry

Closes #1103
Closes #1089

AI Description
• Add /cherry-pick-retry and /rebase issue-comment commands to unblock bot-driven workflows.
• Run pre-commit after cherry-pick and auto-commit any formatter fixes before pushing.
• Harden command execution by gating previously unguarded commands with repo ownership checks.
Diagram
graph TD
  A["Issue comment"] --> B["IssueCommentHandler"] --> C["OwnersFileHandler"]
  B --> D["RunnerHandler"] --> E["Local git worktree"] --> F{{"GitHub API/remote"}}
  D --> G["Pre-commit"] --> E
  B --> H["PullRequestHandler"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use GitHub's built-in 'Update branch' / merge-upstream flow
  • ➕ Avoids local git worktree + force-push complexity
  • ➕ Leverages GitHub-native conflict reporting and permissions
  • ➖ Not always available/enabled for all repos and branch protection rules
  • ➖ Doesn't cover all rebase semantics; may merge instead of rebase
2. Centralize command authorization via a dispatcher/decorator
  • ➕ Reduces repeated guard boilerplate per command
  • ➕ Makes it harder to accidentally introduce unguarded commands in future
  • ➖ Refactor touches many call sites; higher churn and regression risk
  • ➖ Less explicit per-command control (unless carefully designed)
3. Run formatting fixes only in CI and comment results (no auto-commit)
  • ➕ Avoids bot-authored commits and force-push interactions
  • ➕ Keeps PR history purely authored by humans unless opted-in
  • ➖ Doesn't unblock cherry-pick automation; still requires manual fix+push
  • ➖ Adds latency and back-and-forth for small formatting issues

Recommendation: The PR’s approach is appropriate for a bot-driven maintenance workflow: rebase and cherry-pick actions need local git to be deterministic, and the added authorization gates materially reduce abuse risk. Consider a follow-up to centralize authorization in the command dispatcher to prevent future unguarded commands and reduce repetition.

Grey Divider

File Changes

Enhancement (3)
issue_comment_handler.py Add /cherry-pick-retry + /rebase dispatch and guard sensitive commands +153/-0

Add /cherry-pick-retry + /rebase dispatch and guard sensitive commands

• Registers new command constants and routes '/cherry-pick-retry' and '/rebase' to the appropriate handlers. Adds 'is_user_valid_to_run_commands' checks to previously unguarded commands (e.g., cherry-pick, assign-reviewer(s), check-can-merge, verified, wip, test-oracle). Implements 'process_cherry_pick_retry_command' to validate merged state, require an existing cherry-pick label, close bot-owned prior cherry-pick PRs referencing the original PR, then re-run cherry-pick.

webhook_server/libs/handlers/issue_comment_handler.py


runner_handler.py Run pre-commit auto-fix during cherry-pick and add PR rebase implementation +200/-0

Run pre-commit auto-fix during cherry-pick and add PR rebase implementation

• Enhances 'cherry_pick()' to optionally run pre-commit in the cherry-pick worktree and, if hooks modified files, auto-stage and commit the fixes before pushing. Adds 'rebase_pr()' which rebases the PR head onto its base branch and force-pushes with lease; for bot-owned PRs, it enforces that only the PR assignee (initiator) or maintainers can run the rebase.

webhook_server/libs/handlers/runner_handler.py


constants.py Add command constants for cherry-pick retry and rebase +2/-0

Add command constants for cherry-pick retry and rebase

• Defines 'COMMAND_CHERRY_PICK_RETRY_STR' and 'COMMAND_REBASE_STR' for consistent command parsing and documentation.

webhook_server/utils/constants.py


Tests (2)
test_issue_comment_handler.py Add tests for new commands and authorization guards +510/-0

Add tests for new commands and authorization guards

• Introduces async tests ensuring unauthorized users are blocked from newly guarded commands. Adds coverage for '/cherry-pick-retry' dispatch, argument validation, merged/label preconditions, bot-ownership checks when closing existing cherry-pick PRs, and '/rebase' dispatch/guard behavior.

webhook_server/tests/test_issue_comment_handler.py


test_runner_handler.py Add rebase_pr and cherry-pick pre-commit auto-fix test coverage +418/-0

Add rebase_pr and cherry-pick pre-commit auto-fix test coverage

• Adds a new 'TestRebasePr' suite covering non-open PR rejection, bot-owned PR authorization (assignee/maintainer), worktree failures, conflict handling with abort, and success paths. Adds 'TestCherryPickPreCommitAutoFix' to validate pre-commit execution, auto-commit behavior when hooks modify files, and skipping when disabled.

webhook_server/tests/test_runner_handler.py


Documentation (1)
pull_request_handler.py Expose new commands in the welcome/help message +5/-1

Expose new commands in the welcome/help message

• Extends the generated welcome section to document '/cherry-pick-retry' and adds a new 'Branch Management' section for '/rebase'. Ensures rebase help text appears even when cherry-pick operations are not enabled.

webhook_server/libs/handlers/pull_request_handler.py


Grey Divider

Qodo Logo

…ity hardening

- Add /cherry-pick-retry command for retrying failed cherry-picks (#1103)
- Add /rebase command for rebasing PRs onto base branch (#1103)
- Add pre-commit auto-fix after cherry-pick before push (#1089)
- Add is_user_valid_to_run_commands guard to all unguarded commands
- Verify bot ownership before closing cherry-pick PR on retry
- Update welcome message with new commands

Closes #1103
Closes #1089
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used

Grey Divider


Action required

1. app_bot_login exception logged wrongly ✓ Resolved 📘 Rule violation ◔ Observability
Description
In GithubWebhook.process(), the new except Exception path logs with logger.warning(...),
dropping the exception/traceback and violating the required exception-logging standard, which makes
bot-login initialization failures during webhook processing hard to diagnose. Additionally, if
get_repository_github_app_api() returns None/is falsy, app_bot_login can remain unset with no
log at all, potentially silently disabling bot-PR identification and causing incorrect behavior in
bot-PR-dependent flows.
Code

webhook_server/libs/github_api.py[R513-516]

+                except Exception:
+                    self.logger.warning(
+                        f"{self.log_prefix} Failed to get app bot login — bot-PR detection may not work"
+                    )
Evidence
PR Compliance ID 13 requires using logger.exception(...) for exception logging, but the cited code
catches Exception and only emits a warning, which omits the exception details/traceback and
reduces debuggability when initialization fails. The same code path also leaves app_bot_login at
its default empty value when _github_app_api is falsy, without any logging, so a failure to obtain
the GitHub App API (or an unexpected None return) can silently prevent bot-login initialization
and thus impair bot-PR detection without an observable signal.

CLAUDE.md: Use logger.exception() for exception logging; re-raise asyncio.CancelledError
webhook_server/libs/github_api.py[501-516]
webhook_server/libs/github_api.py[194-198]
webhook_server/libs/github_api.py[501-517]

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

## Issue description
`GithubWebhook.process()` has insufficient diagnostics when initializing `self.app_bot_login`: exceptions are caught and logged with `logger.warning(...)`, which loses the exception/traceback and violates the exception-logging compliance requirement (use `logger.exception(...)` in `except` blocks, and re-raise `asyncio.CancelledError` if it could be caught). Also, when the GitHub App API object is missing/falsy, there is no log and `app_bot_login` stays empty, which can silently disable bot-PR identification.

## Issue Context
`self.app_bot_login` is used for bot-PR identification logic; if initialization fails (either via an exception or because `_github_app_api` is `None`/falsy), downstream bot-PR-dependent behavior can be incorrect and the root cause is hard to diagnose without a traceback or at least an explicit log indicating initialization could not occur.

## Fix Focus Areas
- webhook_server/libs/github_api.py[501-516]

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


2. app_bot_login empty fallback ✓ Resolved 📘 Rule violation ☼ Reliability
Description
GithubWebhook.__init__() performs a blocking PyGithub call (github_app_api.get_user().login)
without wrapping it in github_api_call() and swallows any exception by falling back to an empty
string for app_bot_login. This can block the event loop and can silently break/disable the
bot-ownership checks used to gate behaviors in flows like cherry-pick retry and /rebase, leading
to misclassification of bot PRs and incorrect routing/cleanup actions.
Code

webhook_server/libs/github_api.py[R194-198]

+        # Store the app's bot login for identifying PRs created by our app
+        try:
+            self.app_bot_login: str = github_app_api.get_user().login
+        except Exception:
+            self.app_bot_login = ""
Evidence
The cited code path initializes app_bot_login by calling github_app_api.get_user().login
directly inside GithubWebhook.__init__, which violates the requirement (Rule 8) that PyGithub
operations be wrapped with github_api_call() to avoid blocking behavior in async contexts. It also
catches exceptions and sets app_bot_login to "", which Rule 6 discourages for required data
because it masks failures; downstream logic in the cherry-pick retry handler and rebase flow
compares open_pr.user.login / pr_user_login to self.github_webhook.app_bot_login to determine
whether a PR was created by the app bot, so an empty-string fallback causes those checks to fail
silently (e.g., skipping closing old bot-created cherry-pick PRs or applying the wrong authorization
rules).

CLAUDE.md: Wrap All Potentially Blocking PyGithub Operations with github_api_call() (No Direct Calls or Raw asyncio.to_thread)
CLAUDE.md: Zero Tolerance for Unnecessary Defensive Programming (Fail Fast; Only Guard Truly Optional/Uncertain Cases)
webhook_server/libs/github_api.py[194-199]
webhook_server/libs/handlers/issue_comment_handler.py[700-712]
webhook_server/libs/handlers/runner_handler.py[1604-1605]
webhook_server/libs/handlers/runner_handler.py[1600-1606]

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

## Issue description
`GithubWebhook.__init__()` calls PyGithub (`github_app_api.get_user().login`) directly and catches all exceptions, defaulting `self.app_bot_login` to an empty string. This both violates the requirement to wrap blocking PyGithub operations with `github_api_call()` and introduces a defensive fallback that can mask real failures and silently break bot-ownership logic relied upon by `/cherry-pick-retry` and `/rebase` flows.

## Issue Context
`GithubWebhook` is instantiated inside an async background task, so synchronous PyGithub calls in `__init__` can block the event loop. The resolved `app_bot_login` is used to classify whether a PR was created by the app bot (e.g., `process_cherry_pick_retry_command()` only closes an existing bot-created cherry-pick PR when `open_pr.user.login == self.github_webhook.app_bot_login`, and `rebase_pr()` uses `pr_user_login == self.github_webhook.app_bot_login` to decide whether to apply bot-PR authorization rules); when initialization falls back to `""` on error, these comparisons fail without any diagnostic signal.

## Fix Focus Areas
- webhook_server/libs/github_api.py[194-199]
- webhook_server/libs/handlers/issue_comment_handler.py[700-712]
- webhook_server/libs/handlers/runner_handler.py[1604-1605]

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


3. Cherry-pick ignores precommit failures ✓ Resolved 🐞 Bug ☼ Reliability
Description
In RunnerHandler.cherry_pick(), a failing pre-commit run falls through to the push/PR-create path,
and even git add/git commit failures after auto-fix only emit warnings; this can create
cherry-pick PRs that still fail pre-commit or omit intended auto-fixes while the flow reports
success. As written, pre-commit failures are not surfaced as a failed cherry-pick check/run or a
blocking comment.
Code

webhook_server/libs/handlers/runner_handler.py[R1244-1291]

+                # Run pre-commit auto-fix if enabled for the repo
+                if self.github_webhook.pre_commit:
+                    self.logger.info(f"{self.log_prefix} Running pre-commit on cherry-pick worktree")
+                    pre_commit_cmd = f"uvx --directory {worktree_path} {PREK_STR} run --all-files"
+                    rc_pc, _out_pc, _err_pc = await run_command(
+                        command=pre_commit_cmd,
+                        log_prefix=self.log_prefix,
+                        redact_secrets=[github_token],
+                        mask_sensitive=self.github_webhook.mask_sensitive,
+                    )
+                    if not rc_pc:
+                        # Pre-commit returned non-zero — check if any files were
+                        # actually modified before committing.
+                        rc_diff, out_diff, _ = await run_command(
+                            command=f"{git_cmd} diff --name-only",
+                            log_prefix=self.log_prefix,
+                            redact_secrets=[github_token],
+                            mask_sensitive=self.github_webhook.mask_sensitive,
+                        )
+                        if rc_diff and out_diff.strip():
+                            self.logger.info(f"{self.log_prefix} Pre-commit modified files, committing fixes")
+                            rc_add, _, err_add = await run_command(
+                                command=f"{git_cmd} add -A",
+                                log_prefix=self.log_prefix,
+                                redact_secrets=[github_token],
+                                mask_sensitive=self.github_webhook.mask_sensitive,
+                            )
+                            if not rc_add:
+                                self.logger.warning(f"{self.log_prefix} git add failed after pre-commit fix: {err_add}")
+                            rc_commit, _, err_commit = await run_command(
+                                command=(
+                                    f"{git_cmd} commit -m"
+                                    f" {shlex.quote('pre-commit auto-fix for cherry-pick')}"
+                                    " --no-verify"
+                                ),
+                                log_prefix=self.log_prefix,
+                                redact_secrets=[github_token],
+                                mask_sensitive=self.github_webhook.mask_sensitive,
+                            )
+                            if not rc_commit:
+                                self.logger.warning(
+                                    f"{self.log_prefix} git commit failed after pre-commit fix: {err_commit}"
+                                )
+                        else:
+                            self.logger.debug(f"{self.log_prefix} Pre-commit failed but no files modified")
+                    else:
+                        self.logger.debug(f"{self.log_prefix} Pre-commit passed without modifications")
+
Evidence
The pre-commit failure path only logs debug/warnings and then continues into the push step;
run_command() clearly reports non-zero as False, so failures are being ignored by control flow.

webhook_server/libs/handlers/runner_handler.py[1244-1304]
webhook_server/utils/helpers.py[301-404]

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

## Issue description
Pre-commit failures (and auto-fix commit failures) do not stop the cherry-pick flow. The bot can proceed to push and create a PR, yielding confusing or incorrect results.

## Issue Context
`run_command()` returns `False` for non-zero return codes; the current logic only attempts to commit when pre-commit failed *and* produced diffs, and it doesn’t abort when `git add`/`git commit` fail.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1244-1304]
- webhook_server/utils/helpers.py[301-404]

## Suggested implementation notes
- If pre-commit returns non-zero and there are no modifications to commit, treat as a hard failure: set the cherry-pick check-run to failure and comment with the redacted output.
- If auto-fix is attempted and `git add` or `git commit` fails, abort and report failure (don’t continue to push).
- After committing fixes, rerun pre-commit once to confirm it now passes; fail if it still doesn’t.

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


View more (2)
4. Rebase may clobber branches ✓ Resolved 🐞 Bug ⛨ Security
Description
RunnerHandler.rebase_pr() force-pushes origin <head_ref> without verifying the PR head
repository matches the cloned base-repo remote, so fork PRs (or same-name refs) can fail or
overwrite a base-repo branch. It also checks out head_ref even though the clone/worktree is
prepared around the fetched refs/remotes/origin/pr/<number> ref, making the rebase target
ambiguous for non-base-repo heads.
Code

webhook_server/libs/handlers/runner_handler.py[R1535-1650]

+        pr_user_login = await github_api_call(
+            lambda: pull_request.user.login, logger=self.logger, log_prefix=self.log_prefix
+        )
+
+        # Check if PR is bot-owned (e.g., cherry-pick PRs created by the app)
+        is_bot_pr = pr_user_login in self.github_webhook.auto_verified_and_merged_users
+
+        if is_bot_pr:
+            # For bot-owned PRs, validate the user is the PR assignee or a maintainer
+            assignees = await github_api_call(
+                lambda: [a.login for a in pull_request.assignees],
+                logger=self.logger,
+                log_prefix=self.log_prefix,
+            )
+            maintainers = await self.owners_file_handler.get_all_repository_maintainers()
+            if reviewed_user not in assignees and reviewed_user not in maintainers:
+                msg = (
+                    f"@{reviewed_user} is not authorized to rebase this bot-owned PR.\n"
+                    "Only the PR assignee (cherry-pick initiator) or maintainers can rebase."
+                )
+                self.logger.debug(f"{self.log_prefix} {msg}")
+                await github_api_call(
+                    pull_request.create_issue_comment, msg, logger=self.logger, log_prefix=self.log_prefix
+                )
+                return
+
+        base_ref = await github_api_call(lambda: pull_request.base.ref, logger=self.logger, log_prefix=self.log_prefix)
+        head_ref = await github_api_call(lambda: pull_request.head.ref, logger=self.logger, log_prefix=self.log_prefix)
+        github_token = self.github_webhook.token
+
+        self.logger.info(f"{self.log_prefix} Rebasing {head_ref} onto {base_ref}")
+
+        async with self._checkout_worktree(pull_request=pull_request, skip_merge=True) as (
+            success,
+            worktree_path,
+            out,
+            err,
+        ):
+            if not success:
+                msg = "Failed to prepare worktree for rebase"
+                self.logger.error(f"{self.log_prefix} {msg}: {out} --- {err}")
+                await github_api_call(
+                    pull_request.create_issue_comment, msg, logger=self.logger, log_prefix=self.log_prefix
+                )
+                return
+
+            git_cmd = f"git --work-tree={worktree_path} --git-dir={worktree_path}/.git"
+
+            # Checkout the PR head branch
+            rc, out, err = await run_command(
+                command=f"{git_cmd} checkout {head_ref}",
+                log_prefix=self.log_prefix,
+                redact_secrets=[github_token],
+                mask_sensitive=self.github_webhook.mask_sensitive,
+            )
+            if not rc:
+                redacted_err = _redact_secrets(err, [github_token], mask_sensitive=self.github_webhook.mask_sensitive)
+                msg = f"Failed to checkout branch `{head_ref}`: {redacted_err}"
+                self.logger.error(f"{self.log_prefix} {msg}")
+                await github_api_call(
+                    pull_request.create_issue_comment, msg, logger=self.logger, log_prefix=self.log_prefix
+                )
+                return
+
+            # Fetch the base branch
+            rc, out, err = await run_command(
+                command=f"{git_cmd} fetch origin {base_ref}",
+                log_prefix=self.log_prefix,
+                redact_secrets=[github_token],
+                mask_sensitive=self.github_webhook.mask_sensitive,
+            )
+            if not rc:
+                redacted_err = _redact_secrets(err, [github_token], mask_sensitive=self.github_webhook.mask_sensitive)
+                msg = f"Failed to fetch base branch `{base_ref}`: {redacted_err}"
+                self.logger.error(f"{self.log_prefix} {msg}")
+                await github_api_call(
+                    pull_request.create_issue_comment, msg, logger=self.logger, log_prefix=self.log_prefix
+                )
+                return
+
+            # Rebase onto base branch
+            rc, out, err = await run_command(
+                command=f"{git_cmd} rebase origin/{base_ref}",
+                log_prefix=self.log_prefix,
+                redact_secrets=[github_token],
+                mask_sensitive=self.github_webhook.mask_sensitive,
+            )
+            if not rc:
+                # Abort the rebase to clean up
+                await run_command(
+                    command=f"{git_cmd} rebase --abort",
+                    log_prefix=self.log_prefix,
+                    redact_secrets=[github_token],
+                    mask_sensitive=self.github_webhook.mask_sensitive,
+                )
+                redacted_err = _redact_secrets(err, [github_token], mask_sensitive=self.github_webhook.mask_sensitive)
+                redacted_out = _redact_secrets(out, [github_token], mask_sensitive=self.github_webhook.mask_sensitive)
+                msg = (
+                    f"**Rebase failed** for `{head_ref}` onto `{base_ref}`:\n"
+                    f"```\n{redacted_out}\n{redacted_err}\n```\n"
+                    "Please resolve conflicts manually."
+                )
+                self.logger.error(f"{self.log_prefix} Rebase failed: {redacted_out} --- {redacted_err}")
+                await github_api_call(
+                    pull_request.create_issue_comment, msg, logger=self.logger, log_prefix=self.log_prefix
+                )
+                return
+
+            # Force push the rebased branch
+            rc, out, err = await run_command(
+                command=f"{git_cmd} push --force-with-lease origin {head_ref}",
+                log_prefix=self.log_prefix,
+                redact_secrets=[github_token],
+                mask_sensitive=self.github_webhook.mask_sensitive,
+            )
+            if not rc:
Evidence
The repo clone is created from the base repository clone URL and fetches the PR ref into
refs/remotes/origin/pr/<number>, while rebase_pr() checks out and force-pushes head_ref to
origin (base remote) without verifying the head repo matches the base repo.

webhook_server/libs/handlers/runner_handler.py[1515-1657]
webhook_server/libs/github_api.py[318-388]
webhook_server/utils/helpers.py[686-740]

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

## Issue description
`rebase_pr()` force-pushes to `origin <head_ref>` using the base repo clone, but does not validate that the PR head repo is the same as the base repo. For fork PRs (or ref-name collisions), this can either fail or force-push an unintended branch in the base repository.

## Issue Context
- The base repo clone is created from `repository.clone_url` and fetches PR refs into `refs/remotes/origin/pr/<pr_number>`.
- `rebase_pr()` should either (a) refuse to operate on fork PRs, or (b) add/fetch the head remote and push back to the head repo safely.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1515-1666]
- webhook_server/libs/github_api.py[318-388]
- webhook_server/utils/helpers.py[686-740]

## Suggested implementation notes
- Validate `pull_request.head.repo.full_name == self.github_webhook.repository_full_name` before any checkout/rebase/push; if not, comment and return.
- Create/reset a local branch from the fetched PR ref before rebasing, e.g. `git checkout -B <safe_head_branch> origin/pr/<pr_number>` (or use `pull_request.head.sha`).
- When running `git checkout/fetch/push`, pass `--` before ref names or otherwise ensure refs starting with `-` can’t be interpreted as options.

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


5. Rebase authorization too broad ✓ Resolved 🐞 Bug ⛨ Security
Description
/rebase is only gated by OwnersFileHandler.is_user_valid_to_run_commands(), but that set
includes repository contributors/reviewers; rebase_pr() then performs a push --force-with-lease,
allowing non-maintainers to rewrite other users’ PR branches. The additional authorization check
only applies to bot-owned PRs, not normal PRs.
Code

webhook_server/libs/handlers/issue_comment_handler.py[R307-313]

+        elif _command == COMMAND_REBASE_STR:
+            if not await self.owners_file_handler.is_user_valid_to_run_commands(
+                pull_request=pull_request, reviewed_user=reviewed_user
+            ):
+                return
+            await self.runner_handler.rebase_pr(pull_request=pull_request, reviewed_user=reviewed_user)
+
Evidence
The /rebase dispatch checks only is_user_valid_to_run_commands(), and that method’s valid set
includes repository_contributors and all_pull_request_reviewers, while rebase_pr()
force-pushes.

webhook_server/libs/handlers/issue_comment_handler.py[307-313]
webhook_server/libs/handlers/owners_files_handler.py[475-525]
webhook_server/libs/handlers/runner_handler.py[1643-1646]

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

## Issue description
`/rebase` is destructive (force-push) but is currently authorized using `is_user_valid_to_run_commands()`, which includes broad categories (contributors/reviewers). This allows users who are not PR owners/maintainers to trigger history rewrites.

## Issue Context
`is_user_valid_to_run_commands()` is designed for non-destructive commands (retest, etc.) and is too permissive for a force-push operation.

## Fix Focus Areas
- webhook_server/libs/handlers/issue_comment_handler.py[307-313]
- webhook_server/libs/handlers/owners_files_handler.py[475-525]
- webhook_server/libs/handlers/runner_handler.py[1515-1657]

## Suggested implementation notes
- Add an explicit authorization check for `/rebase`, e.g. `reviewed_user == pull_request.user.login` OR `reviewed_user in maintainers`.
- Consider enforcing the same rule inside `RunnerHandler.rebase_pr()` as defense-in-depth (so future callers can’t bypass it).

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



Remediation recommended

6. Blocking app API init 🐞 Bug ☼ Reliability
Description
GithubWebhook.process() calls get_repository_github_app_api() synchronously inside an async method
even though it performs file I/O and GitHub App integration setup, which can block the event loop
during webhook handling. Under concurrent webhook load this can increase latency and risk timeouts
for unrelated requests.
Code

webhook_server/libs/github_api.py[R501-506]

+        # Initialize app bot login for bot-PR identification (async)
+        if not self.app_bot_login:
+            _github_app_api = get_repository_github_app_api(
+                config_=self.config, repository_name=self.repository_full_name
+            )
+            if _github_app_api:
Evidence
The new code path calls a synchronous helper from inside async def process(). The helper itself
does blocking file I/O when reading the private key, so doing this in the event loop can stall
concurrent webhook processing.

webhook_server/libs/github_api.py[498-517]
webhook_server/utils/github_repository_settings.py[410-425]

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

### Issue description
`GithubWebhook.process()` synchronously calls `get_repository_github_app_api()` even though that helper does blocking work (reads the PEM key from disk and builds an integration client). Because `process()` is `async`, this can block the event loop.

### Issue Context
- `process()` already runs in the webhook async processing path.
- `get_repository_github_app_api()` performs blocking file I/O (`open(...pem)`) and integration setup.

### Fix Focus Areas
- webhook_server/libs/github_api.py[501-506]

### Suggested fix
- Prefer reusing the GitHub App API instance obtained during `__init__` (store it as `self.github_app_api`), instead of re-reading the key and reconstructing the integration.
- If re-construction must remain in `process()`, wrap it in `await asyncio.to_thread(...)` (or an existing internal non-blocking helper) to avoid event-loop blocking.

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


7. open_pr.number unwrapped PyGithub access 📘 Rule violation ☼ Reliability
Description
In process_cherry_pick_retry_command, the new logging uses open_pr.number directly inside async
code instead of wrapping the PyGithub property read with github_api_call(). This can introduce
blocking PyGithub access without retries, contrary to the required non-blocking/retry pattern.
Code

webhook_server/libs/handlers/issue_comment_handler.py[R705-707]

+                self.logger.debug(
+                    f"{self.log_prefix} Cherry-pick retry: PR #{open_pr.number} author type "
+                    f"is '{pr_user_type}', not 'Bot', skipping"
Evidence
PR Compliance ID 7 requires PyGithub property accesses in async code to be wrapped with
github_api_call(). The added log message interpolates open_pr.number directly, bypassing the
required wrapper and retry behavior.

CLAUDE.md: All PyGithub operations must be non-blocking and retried via github_api_call (no direct calls or raw asyncio.to_thread)
webhook_server/libs/handlers/issue_comment_handler.py[705-707]

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

## Issue description
Async code in `process_cherry_pick_retry_command()` reads the PyGithub `PullRequest.number` property directly (`open_pr.number`) for log messages. Per the checklist, PyGithub property reads in async paths should be wrapped with `github_api_call()` to avoid blocking the event loop and to ensure retry behavior.

## Issue Context
This code runs while scanning open PRs for a matching cherry-pick PR to close during `/cherry-pick-retry`.

## Fix Focus Areas
- webhook_server/libs/handlers/issue_comment_handler.py[705-707]

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


8. Retry skips non-bot PRs 🐞 Bug ≡ Correctness
Description
process_cherry_pick_retry_command() now closes an existing cherry-pick PR only when
open_pr.user.type == "Bot", so cherry-pick PRs created via the fallback (non–GitHub App) token
path can be authored as type "User" and will never be closed on retry. This can leave duplicate
cherry-pick PRs open and defeats the retry flow’s “close existing failed PR” step.
Code

webhook_server/libs/handlers/issue_comment_handler.py[R700-709]

+            # Verify the PR was created by a bot (not a human)
+            pr_user_type = await github_api_call(
+                lambda _pr=open_pr: _pr.user.type, logger=self.logger, log_prefix=self.log_prefix
+            )
+            if pr_user_type != "Bot":
+                self.logger.debug(
+                    f"{self.log_prefix} Cherry-pick retry: PR #{open_pr.number} author type "
+                    f"is '{pr_user_type}', not 'Bot', skipping"
+                )
+                continue
Evidence
The retry logic gates closing on open_pr.user.type == "Bot", but the cherry-pick PR creation path
explicitly falls back to using the webhook token when no GitHub App token is available; in that case
the created PR can be owned by a non-Bot account and will be skipped by the new retry close logic.

webhook_server/libs/handlers/issue_comment_handler.py[700-709]
webhook_server/libs/handlers/runner_handler.py[1373-1389]
webhook_server/utils/github_repository_settings.py[435-459]

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

### Issue description
`/cherry-pick-retry` currently refuses to close an existing cherry-pick PR unless the PR author has `user.type == "Bot"`. However, cherry-pick PR creation can fall back to using the webhook token when a GitHub App installation token is unavailable, and those PRs may be authored by a normal `"User"` account (e.g., a service/PAT account). In that case, retries will not close the old PR.

### Issue Context
Cherry-pick PR creation uses `pr_create_token = app_token or github_token`, where `app_token` may be `None` if the GitHub App isn’t installed/configured.

### Fix Focus Areas
- webhook_server/libs/handlers/issue_comment_handler.py[700-723]

### Suggested fix
Adjust the “close existing PR” eligibility check to identify *this automation’s* cherry-pick PRs more robustly than `user.type == "Bot"`.

Examples of robust signals you can combine:
- Require an app-specific label (e.g., `CherryPicked-from-*`) on the candidate PR, or another label that only this automation applies.
- Or allow both `user.type == "Bot"` **and** a known configured service account login (if the webhook token identity is known/configured).
- Keep the existing title prefix + original PR URL body check as additional constraints.

After broadening the eligibility check, update/extend unit tests as needed to cover the “service user” case.

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


View more (6)
9. Unverified PR closure 🐞 Bug ⛨ Security
Description
process_cherry_pick_retry_command() closes the first open PR whose title prefix matches and whose
body contains the original PR URL, without verifying the PR was created by this app/bot. This can
incorrectly close an unrelated PR that happens to match the same heuristic.
Code

webhook_server/libs/handlers/issue_comment_handler.py[R700-725]

+            # Check if the PR body references the original PR
+            pr_body = await github_api_call(
+                lambda _pr=open_pr: _pr.body or "", logger=self.logger, log_prefix=self.log_prefix
+            )
+            if original_pr_url not in pr_body:
+                self.logger.debug(
+                    f"{self.log_prefix} Cherry-pick retry: PR #{open_pr.number} body does not "
+                    f"contain original PR URL '{original_pr_url}', skipping"
+                )
+                continue
+
+            self.logger.info(f"{self.log_prefix} Closing existing cherry-pick PR #{open_pr.number} for retry")
+            await github_api_call(
+                open_pr.edit,
+                state="closed",
+                logger=self.logger,
+                log_prefix=self.log_prefix,
+            )
+            await github_api_call(
+                open_pr.create_issue_comment,
+                f"Closed by cherry-pick retry requested by @{reviewed_user} on {original_pr_url}",
+                logger=self.logger,
+                log_prefix=self.log_prefix,
+            )
+            closed_old_pr = True
+            break
Evidence
The retry flow closes an open PR immediately after a title-prefix and body substring match, with no
validation that the PR is app/bot-created. Separately, cherry-pick PRs are created with a
deterministic title format and are intended to be app-owned, so the retry logic has enough available
signals to validate ownership before closing.

webhook_server/libs/handlers/issue_comment_handler.py[671-728]
webhook_server/libs/handlers/runner_handler.py[1102-1105]
webhook_server/libs/handlers/runner_handler.py[1373-1375]

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

## Issue description
`process_cherry_pick_retry_command()` closes an open PR based on a title-prefix + body-contains-URL heuristic, but it never verifies that the matched PR is actually a cherry-pick PR created/managed by this app. This can cause the bot to close an unrelated PR that matches the heuristic.

## Issue Context
Cherry-pick PRs created by this system follow a known title format and are created using a GitHub App token (so they are bot-owned). The retry path should therefore validate ownership/identity (author/login/type and/or expected labels/branch/base) before performing `open_pr.edit(state="closed")`.

## Fix Focus Areas
- webhook_server/libs/handlers/issue_comment_handler.py[700-725]

## Suggested fix
Before closing `open_pr`, add checks that strongly identify it as an app-created cherry-pick PR, for example:
- Verify `open_pr.user.type == "Bot"` and the bot login matches the app/bot identity used by this service (config-driven).
- Verify `open_pr.base.ref == target_branch`.
- Verify `open_pr.title` matches the exact format used by `RunnerHandler.cherry_pick()` (already partially done via prefix).
- Verify presence of the expected cherry-pick label on the candidate PR (e.g., `CherryPicked-from-<source_branch>`), or that `open_pr.head.ref` matches the `CherryPicked-...` branch naming convention.

Update/extend tests to cover the new guard (e.g., ensure a matching title/body PR authored by a non-bot is skipped and not closed).

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


10. Bot PR check too broad ✓ Resolved 🐞 Bug ⛨ Security
Description
The code paths in RunnerHandler.rebase_pr() and the cherry-pick retry flow treat any bot-authored
PR with a cherry-pick-* label as “managed by our app,” but cherry-pick-* is merely a scheduling
label that can appear on non-app PRs. This misclassification can enable unintended actions such as
allowing a non-maintainer assignee (otherwise command-authorized) to force-push rebase a non-app bot
PR branch, and increases the risk of auto-closing a non-app bot PR that happens to match the
heuristic.
Code

webhook_server/libs/handlers/runner_handler.py[R1607-1613]

+        # Check if PR is managed by our app (has cherry-pick labels), not any bot PR (e.g. Renovate, Dependabot)
+        pr_labels = await github_api_call(
+            lambda: [label.name for label in pull_request.labels], logger=self.logger, log_prefix=self.log_prefix
+        )
+        is_bot_pr = pr_user_type == "Bot" and any(
+            label.startswith(CHERRY_PICKED_LABEL) or label.startswith("cherry-pick-") for label in pr_labels
+        )
Evidence
The project distinguishes between app-created cherry-pick PR labels (CherryPicked-from-...,
derived from CHERRY_PICKED_LABEL) and the scheduling label prefix (cherry-pick-...,
CHERRY_PICK_LABEL_PREFIX) used on original PRs; therefore, using
label.startswith("cherry-pick-") in an “app-managed bot PR” heuristic can incorrectly match
arbitrary bot PRs that are not created/managed by the app. rebase_pr() relies on this heuristic
when deciding whether an assignee (not only maintainers) is authorized to perform a
rebase/force-push, and the retry/auto-close flow uses the same weakened filter (even if additional
title/body checks narrow matches), increasing the chance of acting on a PR that only coincidentally
carries the scheduling label.

webhook_server/libs/handlers/runner_handler.py[1607-1627]
webhook_server/utils/constants.py[22-25]
webhook_server/libs/handlers/runner_handler.py[1358-1369]
webhook_server/libs/handlers/issue_comment_handler.py[672-705]

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 “managed by our app” detection for bot-authored PRs currently includes `label.startswith("cherry-pick-")` / `CHERRY_PICK_LABEL_PREFIX`, but that prefix is a cherry-pick *scheduling* label used on original PRs and can appear on PRs not created/managed by this app. This can misclassify non-app bot PRs as app-managed, which can (a) allow a non-maintainer assignee to force-push rebase a non-app bot PR branch, and (b) increase the chance that the retry/auto-close flow acts on the wrong PR.

## Issue Context
App-created cherry-pick PRs are labeled with the `CherryPicked-from-...` family (derived from `CHERRY_PICKED_LABEL`), while `cherry-pick-...` is explicitly the scheduling prefix (`CHERRY_PICK_LABEL_PREFIX`) and is not a reliable indicator that an open PR was created by this app. The heuristic should therefore avoid using the scheduling prefix as a signal of app ownership/management.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1607-1613]
- webhook_server/libs/handlers/issue_comment_handler.py[684-700]
- webhook_server/libs/handlers/runner_handler.py[1358-1369]
- webhook_server/utils/constants.py[22-25]

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


11. Bot PR rebase overbroad ✓ Resolved 🐞 Bug ⛨ Security
Description
RunnerHandler.rebase_pr() classifies any PR authored by a GitHub “Bot” (`pull_request.user.type ==
"Bot"`) as a “bot-owned PR” and then authorizes rebasing for any PR assignee, which expands
force-push capability beyond cherry-pick PRs created by this app. Since the repo clone embeds the
server token and rebase_pr() performs push --force-with-lease, any command-authorized assignee
on a bot-authored PR can trigger history rewrites of that PR branch using the server’s credentials.
Code

webhook_server/libs/handlers/runner_handler.py[R1600-1608]

+        pr_user_type = await github_api_call(
+            lambda: pull_request.user.type, logger=self.logger, log_prefix=self.log_prefix
+        )
+        pr_user_login = await github_api_call(
+            lambda: pull_request.user.login, logger=self.logger, log_prefix=self.log_prefix
+        )
+
+        # Check if PR is bot-owned (e.g., cherry-pick PRs created by the app)
+        is_bot_pr = pr_user_type == "Bot"
Evidence
The cited code shows (1) bot PR classification relies only on user.type == "Bot", (2) bot PRs
authorize assignees, (3) the repo is cloned with an embedded token, and (4) rebase_pr()
force-pushes—together allowing assignees of bot-authored PRs to trigger token-authenticated history
rewrites.

webhook_server/libs/handlers/runner_handler.py[1600-1637]
webhook_server/libs/handlers/runner_handler.py[1725-1731]
webhook_server/libs/github_api.py[318-326]
webhook_server/libs/handlers/owners_files_handler.py[475-524]

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

## Issue description
`rebase_pr()` treats all bot-authored PRs as “bot-owned” and allows *assignees* to trigger a force-push rebase. This should be limited to bot PRs that are known to be created/managed by this app (e.g., cherry-pick PRs), otherwise bot-authored PRs from other automation can be force-pushed by assignees.

## Issue Context
- The local clone uses an embedded GitHub token in the remote URL; `rebase_pr()` later force-pushes.
- `OwnersFileHandler.is_user_valid_to_run_commands()` can allow a broad set of users (contributors/collaborators/reviewers) to invoke `/rebase`.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1600-1608]

### Implementation notes
- Narrow `is_bot_pr` to “app-managed bot PR” rather than `user.type == "Bot"`.
- Example: fetch PR labels and only allow assignee-based authorization when the PR carries an app-specific marker (e.g., any label name starting with `CHERRY_PICKED_LABEL`, or another unmistakable label/title/body marker you already set during cherry-pick PR creation).
- For bot-authored PRs that are *not* app-managed, require maintainers-only (do not allow assignee-based authorization).

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


12. Logs unredacted command stderr ✓ Resolved 🐞 Bug ⛨ Security
Description
In RunnerHandler.cherry_pick(), the git diff failure path logs err_diff directly even though
run_command() returns unredacted stdout/stderr to callers, which can leak secrets into server
logs. This defeats the redaction intent of passing redact_secrets to run_command() and creates a
latent secret-exposure risk if git ever emits sensitive data in stderr.
Code

webhook_server/libs/handlers/runner_handler.py[R1264-1266]

+                            # git diff itself failed — report as worktree error, not pre-commit
+                            self.logger.error(f"{self.log_prefix} git diff failed after pre-commit: {err_diff}")
+                            output["text"] = self.check_run_handler.get_check_run_text(err=err_diff, out=out_diff)
Evidence
The new code path logs err_diff directly from run_command(). run_command() documents and
implements that it returns unredacted stdout/stderr to callers and only redacts for its own internal
logging, so logging the returned stderr reintroduces the possibility of leaking secrets.

webhook_server/libs/handlers/runner_handler.py[1257-1271]
webhook_server/utils/helpers.py[301-327]

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

### Issue description
`RunnerHandler.cherry_pick()` logs `err_diff` from `run_command()` directly on the git-diff error path. `run_command()` explicitly returns **unredacted** stdout/stderr to its callers (redaction is only applied to its own internal logging), so logging `err_diff` can expose secrets in server logs.

### Issue Context
Other error paths in `cherry_pick()` redact `out`/`err` before logging (using `_redact_secrets(...)`), but this new `git diff failed after pre-commit` path does not.

### Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1263-1266]

### Implementation notes
- Redact `err_diff` (and optionally `out_diff`) before logging, e.g.:
 - `redacted_err = _redact_secrets(err_diff, [github_token], mask_sensitive=self.github_webhook.mask_sensitive)`
 - log `redacted_err` instead of `err_diff`
- Alternatively, remove the explicit `logger.error(...)` line since `run_command()` already logs a redacted failure, and keep the check-run/comment behavior unchanged.

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


13. Misreports git diff failure ✓ Resolved 🐞 Bug ◔ Observability
Description
In RunnerHandler.cherry_pick()'s pre-commit failure handling, a failing git diff --name-only
(rc_diff=False) falls into the generic “unfixable pre-commit errors” branch, so the check
run/comment can misattribute the failure to pre-commit instead of a git/worktree problem. This makes
debugging significantly harder and can send operators to the wrong remediation path.
Code

webhook_server/libs/handlers/runner_handler.py[R1308-1319]

+                            # Pre-commit failed with no fixable changes — abort
+                            self.logger.error(f"{self.log_prefix} Pre-commit failed with unfixable errors")
+                            output["text"] = self.check_run_handler.get_check_run_text(err=_err_pc, out=_out_pc)
+                            await self.check_run_handler.set_check_failure(name=CHERRY_PICKED_LABEL, output=output)
+                            await github_api_call(
+                                pull_request.create_issue_comment,
+                                "Cherry-pick pre-commit check failed with unfixable errors. "
+                                "Manual intervention needed.",
+                                logger=self.logger,
+                                log_prefix=self.log_prefix,
+                            )
+                            return
Evidence
The cherry-pick pre-commit flow discards git diff stderr and only checks `if rc_diff and
out_diff.strip(). Any git diff` failure (rc_diff=False) is therefore treated as “no fixable
changes” and reported as an unfixable pre-commit failure, even though the actual failure may be
git/worktree related. run_command() defines rc==False as a non-zero return code, confirming
rc_diff=False indicates git diff failed.

webhook_server/libs/handlers/runner_handler.py[1244-1319]
webhook_server/utils/helpers.py[301-403]

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

## Issue description
When pre-commit returns non-zero, the code runs `git diff --name-only` to see if files were modified. If that `git diff` command fails (rc_diff=False), the current logic falls into the same `else` branch as “no modified files” and emits a generic “Pre-commit failed with unfixable errors” message while also discarding `git diff` stderr. This misreports the root cause and hampers debugging.

## Issue Context
`run_command()` returns `(success: bool, stdout: str, stderr: str)`, so `rc_diff=False` means the `git diff` command itself failed. That should be surfaced distinctly from “pre-commit failed and produced no changes”.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[1257-1319]

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


14. Auto-fix commit unsigned ✓ Resolved 🐞 Bug ≡ Correctness
Description
Cherry-pick explicitly amends commits for DCO/Signed-off-by compliance, but the new pre-commit
auto-fix commit is created without -s/--signoff, which can break DCO enforcement if it applies to
all commits in the cherry-pick PR. The auto-fix commit is also created after the DCO-author
restoration step, so it won’t inherit the corrected trailers automatically.
Code

webhook_server/libs/handlers/runner_handler.py[R1273-1287]

+                            rc_commit, _, err_commit = await run_command(
+                                command=(
+                                    f"{git_cmd} commit -m"
+                                    f" {shlex.quote('pre-commit auto-fix for cherry-pick')}"
+                                    " --no-verify"
+                                ),
+                                log_prefix=self.log_prefix,
+                                redact_secrets=[github_token],
+                                mask_sensitive=self.github_webhook.mask_sensitive,
+                            )
+                            if not rc_commit:
+                                self.logger.warning(
+                                    f"{self.log_prefix} git commit failed after pre-commit fix: {err_commit}"
+                                )
+                        else:
Evidence
The code comments and logic show explicit DCO/signoff handling for cherry-picks, but the new
auto-fix commit does not add a signoff trailer.

webhook_server/libs/handlers/runner_handler.py[843-913]
webhook_server/libs/handlers/runner_handler.py[1244-1287]

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 pre-commit auto-fix commit is created without a Signed-off-by trailer, which may violate DCO/signoff requirements (especially since the code already contains DCO-specific handling for cherry-picks).

## Issue Context
`_restore_original_author_for_cherry_pick()` exists specifically to avoid DCO failures by fixing author/signoff on the cherry-picked commit, but the subsequent auto-fix commit is created via `git commit ... --no-verify` without `-s`.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[843-913]
- webhook_server/libs/handlers/runner_handler.py[1244-1287]

## Suggested implementation notes
- Create the auto-fix commit with `git commit -s ...`.
- If your DCO policy requires signoff to match author, also ensure `user.name/user.email` or `--author` is set appropriately for the auto-fix commit.

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



Informational

15. Retry accepts multiple branches ✓ Resolved 🐞 Bug ≡ Correctness
Description
process_cherry_pick_re...

@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

Review & Approval

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

Testing & Validation

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

Container Operations

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

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

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

✅ Merge Requirements

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

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 1 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No wip, hold, 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

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

Comment thread webhook_server/libs/handlers/runner_handler.py
Comment thread webhook_server/libs/handlers/issue_comment_handler.py
Comment thread webhook_server/libs/handlers/runner_handler.py
Comment thread webhook_server/libs/handlers/runner_handler.py
Comment thread webhook_server/libs/handlers/issue_comment_handler.py
…commit abort, DCO signoff

- Reject rebase on fork PRs (head repo != base repo)
- Restrict non-bot PR rebase to PR owner or maintainers only
- Abort cherry-pick when pre-commit fails with unfixable errors
- Abort cherry-pick when git add/commit fails after pre-commit fix
- Add --signoff to pre-commit auto-fix commit for DCO compliance
- Validate single branch name in /cherry-pick-retry
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

Comment thread webhook_server/libs/handlers/runner_handler.py
@myakove

myakove commented Jun 9, 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/handlers/runner_handler.py:1535 (qodo bug) — Rebase may clobber branches

Addressed: Fixed in commit e3e4e57 — added head.repo.full_name check, rejects fork PRs with comment.

webhook_server/libs/handlers/issue_comment_handler.py:307 (qodo bug) — Rebase authorization too broad

Addressed: Fixed in commit e3e4e57 — non-bot PRs restricted to PR owner or maintainers only.

webhook_server/libs/handlers/runner_handler.py:1244 (qodo bug) — Cherry-pick ignores precommit failures

Addressed: Fixed in commit e3e4e57 — pre-commit failure with no fixable changes now aborts cherry-pick. git add/commit failures also abort.

webhook_server/libs/handlers/runner_handler.py:1273 (qodo bug) — Auto-fix commit unsigned

Addressed: Fixed in commit e3e4e57 — added --signoff to pre-commit auto-fix commit for DCO compliance.

webhook_server/libs/handlers/issue_comment_handler.py:630 (qodo bug) — Retry accepts multiple branches

Addressed: Fixed in commit e3e4e57 — validates single branch name, rejects multi-branch input.

- When git diff --name-only fails after pre-commit, report as worktree error
- Previously fell into unfixable pre-commit errors branch, misattributing cause
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 86510ca

Comment thread webhook_server/libs/handlers/runner_handler.py Outdated
Redact secrets in git diff error logging after pre-commit.
Reformat long lines for readability.
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

- Cherry-pick PRs are created by the GitHub App bot, not in auto_verified_and_merged_users
- Check user.type == Bot to detect any bot-created PR
- Fixes cherry-pick-retry not closing old cherry-pick PRs
- Also fixes rebase bot-PR ownership detection
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9c00066

Comment thread webhook_server/libs/handlers/runner_handler.py Outdated
- user.type == Bot is too broad (catches Renovate, Dependabot, etc.)
- Now also checks for cherry-pick labels to confirm it is our app PR
- Prevents unauthorized force-push rebase on non-cherry-pick bot PRs
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

…match, simplify bot check

- Wrap pull_request.html_url in github_api_call to avoid blocking/stale data
- Remove over-restrictive bot-ownership and label filters that prevented matches
- Simplify close logic to title prefix + body URL match (sufficient for correctness)
- Add debug logging at every step: PR count, title match/skip, body URL match/skip, close/no-match
- Update rebase bot-PR detection to use user.type == "Bot" with cherry-pick label check
- Update tests to match simplified close logic and bot detection changes
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 141108d

Comment thread webhook_server/libs/handlers/issue_comment_handler.py
- Restore Bot type verification before closing cherry-pick PRs on retry
- Prevents closing human-created PRs that happen to match title/body pattern
- Keep debug logging at every step (title, user type, body URL)
- Add test for human-created PR skip (user.type == "User")
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

Comment thread webhook_server/libs/handlers/issue_comment_handler.py Outdated
Comment thread webhook_server/libs/handlers/issue_comment_handler.py Outdated
… rebase

- Store GitHub App bot login on GithubWebhook during init (github_api.py)
- Cherry-pick retry: check PR author login matches app_bot_login instead of user.type
- Rebase: check PR author login matches app_bot_login instead of user.type + label heuristic
- Prevents false matches on Renovate, Dependabot, or other bot PRs
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

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

myakove commented Jun 9, 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/handlers/issue_comment_handler.py:705 (qodo rule violation) — open_pr.number unwrapped PyGithub access

Addressed: Already fixed in commit 6df198e — the close logic verifies pr.user.login == self.github_webhook.app_bot_login (our specific GitHub App's bot identity). Three-filter validation: (1) title prefix match, (2) author is our app's bot login, (3) body contains original PR URL. Non-app PRs are skipped.

webhook_server/libs/handlers/issue_comment_handler.py:700 (qodo bug) — Retry skips non-bot PRs

Addressed: Already fixed in commit 6df198e — the close logic verifies pr.user.login == self.github_webhook.app_bot_login (our specific GitHub App's bot identity). Three-filter validation: (1) title prefix match, (2) author is our app's bot login, (3) body contains original PR URL. Non-app PRs are skipped.

webhook_server/libs/handlers/issue_comment_handler.py:700 (qodo bug) — Unverified PR closure

Addressed: Already fixed in commit 6df198e — the close logic verifies pr.user.login == self.github_webhook.app_bot_login (our specific GitHub App's bot identity). Three-filter validation: (1) title prefix match, (2) author is our app's bot login, (3) body contains original PR URL. Non-app PRs are skipped.

- Replace blocking github_app_api.get_user().login in __init__ with async github_api_call in process()
- Log warning on failure instead of silent empty fallback
- Guard with "if not self.app_bot_login" to avoid redundant API calls
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 58a530e

Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/libs/github_api.py
@myakove

myakove commented Jun 9, 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:513 (qodo rule violation) — app_bot_login exception logged wrongly

Addressed: Fixed in commit 58a530e — app_bot_login init moved to async process() with github_api_call() wrapper and explicit logger.warning on failure.

webhook_server/libs/github_api.py:501 (qodo bug) — Blocking app API init

Addressed: Fixed in commit 58a530e — removed blocking PyGithub call from init, moved to process() using github_api_call() which wraps in asyncio.to_thread with retry.

…ncelledError, log missing API

- Use logger.exception instead of logger.warning to preserve traceback
- Add except asyncio.CancelledError: raise before broad except
- Log debug message when GitHub App API is not available
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

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.

Allow cherry-pick owner to close cherry-picked PR and re-trigger cherry-pick pre-commit fails doesn't auto resolve when cherry-picking

2 participants