Keep a failed backport from corrupting the PR state by aiolibsbot · Pull Request #81 · sanitizers/patchback-github-app · GitHub
Skip to content

Keep a failed backport from corrupting the PR state - #81

Draft
aiolibsbot wants to merge 1 commit into
sanitizers:masterfrom
aiolibsbot:koan/fail-safe-backport-reporting
Draft

aiolibsbot wants to merge 1 commit into
sanitizers:masterfrom
aiolibsbot:koan/fail-safe-backport-reporting

Conversation

@aiolibsbot

@aiolibsbot aiolibsbot commented Sep 17, 2026 •

Copy link
Copy Markdown

What

Three failure paths in the reporting layer leave the user's repo or check run in a state that never resolves. This fixes all three.

Why

Each one is silent — nothing in the PR or the check run tells anyone it happened:

  1. A locked PR can be left permanently unlocked. start_reporting() unlocks the PR; finish_reporting() re-locks it after updating the tracking comment. If that comment update fails, the maintainer's deliberate lock is gone for good.
  2. Reporting a failure could itself fail. update_comment() patched None as the URI when create_comment() never ran or errored.
  3. One target branch could take the others down with it (the class of failure in ServerDisconnectedError: Server disconnected #42). A PR labeled backport-3.0 and backport-2.9 lost the 2.9 backport entirely — no attempt, no report — when 3.0 hit a transient API error.

How

  • Lock restoration moved into a finally — it's the one side effect that has to survive a failing report.
  • update_comment() is a no-op without a tracking comment, which makes finish_reporting() safe to call unconditionally.
  • The target-branch loop isolates each branch and re-raises the first error after every branch has had its turn, so the error still reaches the logs and Sentry. Exception rather than BaseException is deliberate: task cancellation must keep aborting the loop at once.

Testing

9 tests, stdlib + pytest only. 6 of them fail on master and pass here — every fix is load-bearing.

tests/test_target_branch_isolation.py stubs anyio/gidgethub/octomachinery in sys.modules so the webhook handler stays testable without pulling third-party packages into CI. The decorators become identity functions, which is what these tests want — they exercise the handler body, not the router.

pytest.ini and .github/workflows/tests.yml are byte-identical to the copies on #77/#72/#73, so git merges them add/add cleanly regardless of order.

Not in scope

An exception escaping process_pr_backport_labels() still leaves the check run stuck at in_progress forever. The fix is a catch-all around that function's body, which means reindenting ~130 lines — that would conflict with every other open PR touching it. Worth doing once this stack drains.


Quality Report

Changes: 7 files changed, 399 insertions(+), 19 deletions(-)

Code scan: clean

Tests: failed (command not found)

Branch hygiene: clean

Generated by Kōan

Three related gaps in the failure paths of the reporting layer, all of
which leave the user's repo or check run in a state that never resolves.

* `PullRequestReporter.start_reporting()` unlocks a deliberately locked
  PR and `finish_reporting()` is what puts the lock back. The lock
  restoration ran *after* the terminal comment update, so a failing
  comment update left the PR permanently unlocked. It is now in a
  `finally`, since restoring the maintainer's lock is the one side
  effect that has to survive a failing report.

* `CommentsAPI.update_comment()` sent `None` as the comment URI when
  `create_comment()` had never run or had failed, so reporting a
  terminal failure blew up on its own. It is a no-op now, which makes
  `finish_reporting()` safe to call unconditionally.

* `on_merge_of_labeled_pr()` iterated the target branches with no
  isolation. A PR labeled `backport-3.0` and `backport-2.9` lost the
  2.9 backport entirely -- no attempt, no report -- when 3.0 hit a
  transient API error. Every branch is attempted now and the first
  error is re-raised afterwards so it still reaches the logs and error
  tracking. `Exception` rather than `BaseException` is deliberate:
  task cancellation must keep aborting the loop at once.

The suite stubs `anyio`/`gidgethub`/`octomachinery` so the handler stays
testable without pulling third-party packages into CI.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant