• 
      

    Add connected service account "needs attention" and track GH App state.

    Review Request #15197 — Created July 24, 2026 and updated

    Information

    Review Board
    release-9.x

    Reviewers

    GitHub Apps can be uninstalled or suspended from the GitHub side, which
    can cause our integration to start failing. This change makes us handle
    that in a nice way. This involves several pieces:

    Connected Services "needs attention"

    Accounts can now indicate that they need attention in some way, and
    provide actions to handle this.

    This is now implemented for app-based accounts with GitHub in order to
    allow the admin to reestablish the correction.

    Later work will involve using this for other hosting services (and
    PAT-based GitHub connections) to have accounts indicate when their
    credentials need to be updated.

    GitHub App webhook handler

    The stub webhook handler has been fleshed out to verify the signature
    header and handle several events:

    • deleted and suspend mark the installation account with the state
      so that they'll be flagged in the connected services list.
    • created can heal a reinstall by refreshing the stored installation
      ID, matched to the existing account. This allows a reinstall performed
      entirely from the GitHub side to restore the connection without
      requiring anything to happen inside Review Board.
    • installation_repositories keeps the repository_selection flag up
      to date.
    • installation_target updates the stored account name.

    API request error handling

    In the case of a missed webhook delivery, we can still end up with the
    stored state being wrong (in either direction). API requests have been
    updated to handle this in two ways:

    • Token minting will map a 403 or 404 response to the
      suspended/uninstalled state (checking first to ensure that it's not
      some other ephemeral failure or rate limit error). If we got the error
      because the app had been uninstalled but is now reinstalled, we'll
      adopt the new installation ID.

    • 403 or 404 errors for cached installation tokens will drop the token
      and try again (limited to once per minute).

    • If we have a stored non-active status, we'll recheck that state (also
      limited to once per minute) before refusing to mint a new token.

    GitHub reconnect

    The reconnect action for suspended/uninstalled accounts will first try
    to verify that state (in case of a missed webhook delivery), and if it's
    still an issue, will deep link the user to the relevant page where they
    can fix it.

    • Ran unit tests.
    • Suspended and deleted app installs and saw that the state was properly
      reflected in the connected services list.
    • Reconnected suspended and deleted app installs and saw that state
      healed correctly.
    Summary ID
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn

    Description From Last Updated

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (83 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    Can probably get rid of this.

    maubin maubin

    Would it be better to change the return type to Literal['active', 'suspended', 'removed', '']?

    maubin maubin

    Would it be better to change the return type to Literal['active', 'suspended', 'removed', '']?

    maubin maubin

    Can we use _get_github_urls()['app_base'] here instead?

    maubin maubin

    We may want to do local_site=request.local_site here.

    maubin maubin

    Let's pull out account.login into a variable since we use it a lot below.

    maubin maubin

    redefinition of unused 'GitHubAppInstallationData' from line 36 Column: 5 Error code: F811

    reviewbot reviewbot

    We generally keep TypedDict fields in alphabetical order (in required/not-required groups).

    chipx86 chipx86

    Small nit, but can you move the example to a new paragraph so it stands out better from the description?

    chipx86 chipx86

    These are missing "Version Added". Also to check, what's the benefit to using strings here over an enum?

    chipx86 chipx86

    Can we alphabetize these?

    chipx86 chipx86

    Can this be InstallationStatus?

    chipx86 chipx86

    No parens here.

    chipx86 chipx86

    AI smell. We never use this.

    chipx86 chipx86

    This should be pulled out into a variable.

    chipx86 chipx86

    Blank line required between these.

    chipx86 chipx86

    We should stick with the standard grouping of required, optional. And alphabetical within that. Same as a TypedDict.

    chipx86 chipx86

    Summary must be one line.

    chipx86 chipx86

    These should be in alphabetical order.

    chipx86 chipx86

    Missing parens.

    chipx86 chipx86

    Missing parens, and should be in alphabetical order.

    chipx86 chipx86

    The )) for each string should be on its own line, so the string is self-contained.

    chipx86 chipx86

    No parens needed here.

    chipx86 chipx86

    Looks like we can just do a simple if and return [reconnect_item] or [].

    chipx86 chipx86

    Can this be a Sequence?

    chipx86 chipx86

    This would fit on one line.

    chipx86 chipx86

    Can you update this for alphabetical order?

    chipx86 chipx86

    Should we check explicitly for the removed state? If we add a new state for any reason in the future, …

    chipx86 chipx86

    To be safe, we should escape arguments going into the URL.

    chipx86 chipx86

    To be safe, we should escape values going into URLs.

    chipx86 chipx86

    If this can't be imported top-level, can we import at the top of the function like we typically do?

    chipx86 chipx86

    Can you pull this out into a variable?

    chipx86 chipx86

    These are missing docs.

    chipx86 chipx86

    Can you pull this out into a variable? Helps with readability/maintainability.

    chipx86 chipx86

    SyntaxError: invalid decimal literal Column: 20 Error code: E999

    reviewbot reviewbot

    SyntaxError: invalid decimal literal Column: 20 Error code: E999

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

    flake8

    david
    maubin
    1. 
        
    2. reviewboard/admin/views.py (Diff revision 2)
       
       
      Show all issues

      Can probably get rid of this.

      1. I'm not sure what you mean? This is the format that we need for tuples in return values to parse out.

      2. Oh oops.

    3. reviewboard/hostingsvcs/github/client.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      Would it be better to change the return type to Literal['active', 'suspended', 'removed', '']?

    4. reviewboard/hostingsvcs/github/client.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      Would it be better to change the return type to Literal['active', 'suspended', 'removed', '']?

    5. Show all issues

      Can we use _get_github_urls()['app_base'] here instead?

      1. I'll have to move it to a shared place (it's currently a staticmethod on the view base) but sure.

    6. reviewboard/hostingsvcs/github/views.py (Diff revision 2)
       
       
       
      Show all issues

      We may want to do local_site=request.local_site here.

    7. reviewboard/hostingsvcs/github/views.py (Diff revision 2)
       
       
      Show all issues

      Let's pull out account.login into a variable since we use it a lot below.

    8. 
        
    david
    Review request changed
    Commits:
    Summary ID
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    david
    maubin
    1. 
        
    2. 
        
    chipx86
    1. 
        
    2. reviewboard/hostingsvcs/base/hosting_service.py (Diff revision 4)
       
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      We generally keep TypedDict fields in alphabetical order (in required/not-required groups).

    3. reviewboard/hostingsvcs/base/hosting_service.py (Diff revision 4)
       
       
       
       
      Show all issues

      Small nit, but can you move the example to a new paragraph so it stands out better from the description?

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

      These are missing "Version Added".

      Also to check, what's the benefit to using strings here over an enum?

    5. Show all issues

      Can we alphabetize these?

    6. Show all issues

      Can this be InstallationStatus?

    7. Show all issues

      No parens here.

    8. Show all issues

      AI smell. We never use this.

    9. reviewboard/hostingsvcs/github/accounts.py (Diff revision 4)
       
       
       
       
       
      Show all issues

      This should be pulled out into a variable.

    10. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
       
      Show all issues

      Blank line required between these.

    11. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      We should stick with the standard grouping of required, optional. And alphabetical within that. Same as a TypedDict.

    12. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
       
      Show all issues

      Summary must be one line.

    13. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
       
       
       
       
      Show all issues

      These should be in alphabetical order.

    14. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
      Show all issues

      Missing parens.

    15. reviewboard/hostingsvcs/github/api.py (Diff revision 4)
       
       
       
       
       
       
       
       
      Show all issues

      Missing parens, and should be in alphabetical order.

    16. reviewboard/hostingsvcs/github/client.py (Diff revision 4)
       
       
       
       
       
       
      Show all issues

      The )) for each string should be on its own line, so the string is self-contained.

    17. Show all issues

      No parens needed here.

    18. reviewboard/hostingsvcs/github/service.py (Diff revision 4)
       
       
       
       
       
       
       
       
      Show all issues

      Looks like we can just do a simple if and return [reconnect_item] or [].

    19. Show all issues

      Can this be a Sequence?

    20. reviewboard/hostingsvcs/github/service.py (Diff revision 4)
       
       
       
      Show all issues

      This would fit on one line.

    21. reviewboard/hostingsvcs/github/service.py (Diff revision 4)
       
       
       
       
       
       
      Show all issues

      Can you update this for alphabetical order?

    22. reviewboard/hostingsvcs/github/service.py (Diff revision 4)
       
       
       
      Show all issues

      Should we check explicitly for the removed state? If we add a new state for any reason in the future, this may produce the wrong result.

    23. Show all issues

      To be safe, we should escape arguments going into the URL.

    24. reviewboard/hostingsvcs/github/views.py (Diff revision 4)
       
       
      Show all issues

      To be safe, we should escape values going into URLs.

    25. reviewboard/hostingsvcs/github/views.py (Diff revision 4)
       
       
      Show all issues

      If this can't be imported top-level, can we import at the top of the function like we typically do?

    26. reviewboard/hostingsvcs/github/views.py (Diff revision 4)
       
       
       
       
      Show all issues

      Can you pull this out into a variable?

    27. reviewboard/static/rb/css/pages/admin/connected-services.less (Diff revision 4)
       
       
       
       
       
       
       
       
       
       
      Show all issues

      These are missing docs.

    28. Show all issues

      Can you pull this out into a variable? Helps with readability/maintainability.

    29. 
        
    david
    Review request changed
    Commits:
    Summary ID
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    david
    Review request changed
    Commits:
    Summary ID
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn
    Add connected service account "needs attention" and track GH App state.
    GitHub Apps can be uninstalled or suspended from the GitHub side, which can cause our integration to start failing. This change makes us handle that in a nice way. This involves several pieces: # Connected Services "needs attention" Accounts can now indicate that they need attention in some way, and provide actions to handle this. This is now implemented for app-based accounts with GitHub in order to allow the admin to reestablish the correction. Later work will involve using this for other hosting services (and PAT-based GitHub connections) to have accounts indicate when their credentials need to be updated. # GitHub App webhook handler The stub webhook handler has been fleshed out to verify the signature header and handle several events: - `deleted` and `suspend` mark the installation account with the state so that they'll be flagged in the connected services list. - `created` can heal a reinstall by refreshing the stored installation ID, matched to the existing account. This allows a reinstall performed entirely from the GitHub side to restore the connection without requiring anything to happen inside Review Board. - `installation_repositories` keeps the `repository_selection` flag up to date. - `installation_target` updates the stored account name. # API request error handling In the case of a missed webhook delivery, we can still end up with the stored state being wrong (in either direction). API requests have been updated to handle this in two ways: - Token minting will map a 403 or 404 response to the suspended/uninstalled state (checking first to ensure that it's not some other ephemeral failure or rate limit error). If we got the error because the app had been uninstalled but is now reinstalled, we'll adopt the new installation ID. - 403 or 404 errors for cached installation tokens will drop the token and try again (limited to once per minute). - If we have a stored non-active status, we'll recheck that state (also limited to once per minute) before refusing to mint a new token. # GitHub reconnect The reconnect action for suspended/uninstalled accounts will first try to verify that state (in case of a missed webhook delivery), and if it's still an issue, will deep link the user to the relevant page where they can fix it. Testing Done: - Ran unit tests. - Suspended and deleted app installs and saw that the state was properly reflected in the connected services list. - Reconnected suspended and deleted app installs and saw that state healed correctly.
    ummslvyqxyoqyzlozxkmksvtrxvnrpwn

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.