• 
      

    Add a base class and registry for GitHub App webhook handlers.

    Review Request #15334 — Created Sept. 24, 2026 and updated

    Information

    Review Board
    release-9.x

    Reviewers

    A GitHub App has a single, app-wide webhook URL that receives events from
    every account the app is installed on. We have the GitHubAppWebhookView for
    this, which currently only handles app installation events. We should have an
    easy way for us and extension authors to handle GitHub webhook events too.

    This change introduces the BaseGitHubAppWebhookHandler, which can be
    subclassed to list the events the handler supports and the required GitHub App
    permissions. Events will be passed to handlers as a GitHubAppWebhookEvent,
    containing the event's raw payload, delivery ID, and the account(s) and
    repository it may apply to. The actual dispatching to handlers will come in
    a later change.

    This also adds a registry for the handlers. The registry has a helper for
    merging all the events and required pemissions of the registered handlers,
    so that they can all be shown and requested when creating a GitHub App. The
    hook for registering handlers will come in a later change.

    • Ran unit tests.
    • Used in the Pull Request integration work.
    Summary ID
    Add a base class and registry for GitHub App webhook handlers.
    A GitHub App has a single, app-wide webhook URL that receives events from every account the app is installed on. We have the `GitHubAppWebhookView` for this, which currently only handles app installation events. We should have an easy way for us and extension authors to handle GitHub webhook events too. This change introduces the `BaseGitHubAppWebhookHandler`, which can be subclassed to list the events the handler supports and the required GitHub App permissions. Events will be passed to handlers as a `GitHubAppWebhookEvent`, containing the event's raw payload, delivery ID, and the account(s) and repository it may apply to. The actual dispatching to handlers will come in a later change. This also adds a registry for the handlers. The registry has a helper for merging all the events and required pemissions of the registered handlers, so that they can all be shown and requested when creating a GitHub App. The hook for registering handlers will come in a later change.
    d3317ad8b4747b74e8e46902a7689955d9aa16b0
    Description From Last Updated

    Needs "Version Added"

    david david

    Can we call this name or event_name? So we avoid having code that says event.event?

    david david

    typo: this is value -> this is the value

    david david

    These don't have defaults, and nothing checks them at registration. They'll only fail with an AttributeError later, inside get_events() or …

    david david

    This line is missing a :

    david david

    Parens aren't necessary here.

    david david

    OrderedRegistry.__init__ doesn't take any args. I don't think we need *args or **kwargs passthrough.

    david david

    This isn't validating that events is a set. It seems like probably an easy mistake to write: events = 'pull_requests' …

    david david

    It seems like this exception will get caught by the extension hook, which will also log. Can we check and …

    david david

    This should probably return a Sequence

    david david

    Do we want to return this as a frozenset so that callers can't potentially mutate the cache?

    david david

    Should this return Mapping?

    david david

    Mapping

    david david

    self._events is still typed as set[str], and we're returning that internal cache directly. Can we type that as frozenset, and …

    david david

    Mapping

    david david

    This doesn't use self at all. Can we make it module-level like merge_github_app_permissions?

    david david

    Should be Mapping[str, GitHubAppPermissionLevel]

    david david

    Missing a "with" in here, and the closing """ should go on its own line.

    david david

    Might want to pin what logs we capture here so unrelated things don't cause the test to break: with self.assertLogs('reviewboard.hostingsvcs.github.webhooks', …

    david david

    no newline at end of file Column: 15 Error code: W292

    reviewbot reviewbot
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    david
    1. 
        
    2. Show all issues

      Can we call this name or event_name? So we avoid having code that says event.event?

    3. Show all issues

      typo: this is value -> this is the value

    4. reviewboard/hostingsvcs/github/webhooks.py (Diff revision 1)
       
       
       
       
       
       
       
       
      Show all issues

      These don't have defaults, and nothing checks them at registration. They'll only fail with an AttributeError later, inside get_events() or get_handlers_for_event(). That would cause every webhook delivery to fail, not just the broken one.

      Same for an invalid level set in required_permissions.

      We should probably override on_item_registering and fail loudly if the handler is broken. The SCMTools registry does something like this for reference.

    5. Show all issues

      This line is missing a :

    6. Show all issues

      OrderedRegistry.__init__ doesn't take any args. I don't think we need *args or **kwargs passthrough.

    7. Show all issues

      This should probably return a Sequence

    8. Show all issues

      Do we want to return this as a frozenset so that callers can't potentially mutate the cache?

    9. Show all issues

      Should this return Mapping?

    10. Show all issues

      Mapping

    11. Show all issues

      Mapping

    12. Show all issues

      Should be Mapping[str, GitHubAppPermissionLevel]

    13. Show all issues

      Missing a "with" in here, and the closing """ should go on its own line.

    14. Show all issues

      Might want to pin what logs we capture here so unrelated things don't cause the test to break:

      with self.assertLogs('reviewboard.hostingsvcs.github.webhooks',
                           level='WARNING') as logs:
      
    15. 
        
    maubin
    david
    1. 
        
    2. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
       
      Show all issues

      Needs "Version Added"

    3. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
      Show all issues

      Parens aren't necessary here.

    4. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
       
      Show all issues

      This isn't validating that events is a set. It seems like probably an easy mistake to write:

      events = 'pull_requests'
      

      Which will end up iterating over single characters, sticking 'p', 'u', 'l', ... into the github app manifest.

    5. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
       
       
       
       
       
       
       
      Show all issues

      It seems like this exception will get caught by the extension hook, which will also log. Can we check and make sure we're not adding extra noise by logging here?

    6. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      self._events is still typed as set[str], and we're returning that internal cache directly. Can we type that as frozenset, and then assemble it here:

      events = frozenset(
          event
          for handler in self
          for event in handler.events
      )
      
    7. reviewboard/hostingsvcs/github/webhooks.py (Diff revisions 1 - 2)
       
       
       
       
       
      Show all issues

      This doesn't use self at all. Can we make it module-level like merge_github_app_permissions?

    8. 
        
    maubin
    Review request changed
    Commits:
    Summary ID
    Add a base class and registry for GitHub App webhook handlers.
    A GitHub App has a single, app-wide webhook URL that receives events from every account the app is installed on. We have the `GitHubAppWebhookView` for this, which currently only handles app installation events. We should have an easy way for us and extension authors to handle GitHub webhook events too. This change introduces the `BaseGitHubAppWebhookHandler`, which can be subclassed to list the events the handler supports and the required GitHub App permissions. Events will be passed to handlers as a `GitHubAppWebhookEvent`, containing the event's raw payload, delivery ID, and the account(s) and repository it may apply to. The actual dispatching to handlers will come in a later change. This also adds a registry for the handlers. The registry has a helper for merging all the events and required pemissions of the registered handlers, so that they can all be shown and requested when creating a GitHub App. The hook for registering handlers will come in a later change.
    d80a8f7e357a8cc5ba2cb2abe6b6784a680efc12
    Add a base class and registry for GitHub App webhook handlers.
    A GitHub App has a single, app-wide webhook URL that receives events from every account the app is installed on. We have the `GitHubAppWebhookView` for this, which currently only handles app installation events. We should have an easy way for us and extension authors to handle GitHub webhook events too. This change introduces the `BaseGitHubAppWebhookHandler`, which can be subclassed to list the events the handler supports and the required GitHub App permissions. Events will be passed to handlers as a `GitHubAppWebhookEvent`, containing the event's raw payload, delivery ID, and the account(s) and repository it may apply to. The actual dispatching to handlers will come in a later change. This also adds a registry for the handlers. The registry has a helper for merging all the events and required pemissions of the registered handlers, so that they can all be shown and requested when creating a GitHub App. The hook for registering handlers will come in a later change.
    d3317ad8b4747b74e8e46902a7689955d9aa16b0

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.
    david
    1. Ship It!
    2.