Skip to content

fix(github-api): handle authentication errors in add_api_users_to_auto_verified_and_merged_users#984

Merged
myakove merged 12 commits intomainfrom
fix/api-auth-error-handling
Feb 19, 2026
Merged

fix(github-api): handle authentication errors in add_api_users_to_auto_verified_and_merged_users#984
myakove merged 12 commits intomainfrom
fix/api-auth-error-handling

Conversation

@rnetser
Copy link
Copy Markdown
Collaborator

@rnetser rnetser commented Jan 22, 2026

User description

Summary

  • Add try/except block to catch GithubException when calling _api.get_user().login in add_api_users_to_auto_verified_and_merged_users() method
  • Include last 4 characters of token in log messages for easier debugging

Problem

When a GitHub API token fails authentication with a 401 error, the server crashed because GithubException was not being caught. The existing rate limit check (== 60) only catches one type of invalid token scenario.

Solution

Added exception handling similar to the pattern used in get_api_with_highest_rate_limit() in helpers.py. Now authentication failures are logged as warnings and processing continues with the next token.

Test plan

  • All 1231 tests pass
  • 90.27% code coverage maintained
  • ruff and mypy checks pass

PR Type

Bug fix


Description

  • Add try/except block to catch GithubException in add_api_users_to_auto_verified_and_merged_users()

  • Include last 4 characters of token in log messages for debugging

  • Prevent server crash on authentication failures by continuing with next token


Diagram Walkthrough

flowchart LR
  A["API Token Processing"] --> B{"Rate Limit == 60?"}
  B -->|Yes| C["Log Warning with Token Suffix"]
  C --> D["Skip to Next Token"]
  B -->|No| E["Try get_user().login"]
  E --> F{"GithubException?"}
  F -->|Yes| G["Log Warning & Skip"]
  F -->|No| H["Add User to List"]
  G --> D
  H --> D
Loading

File Walkthrough

Relevant files
Bug fix
github_api.py
Add exception handling for GitHub API authentication         

webhook_server/libs/github_api.py

  • Changed loop variable from _ to _token to capture token value
  • Extract last 4 characters of token as token_suffix for logging
  • Wrap _api.get_user().login call in try/except block to catch
    GithubException
  • Log warning with token suffix when authentication fails and continue
    processing
  • Update existing rate limit warning message to include token suffix
+13/-3   

Summary by CodeRabbit

  • Performance Improvements

    • Token validation now runs concurrently for faster processing.
  • Reliability

    • Initialization of verified users now occurs as part of the processing flow to ensure consistent population during handling.
    • Blocking calls moved to non-blocking paths to reduce stalls.
  • Tests

    • Test suite updated with asynchronous test variant to validate the new async processing behavior.

…o_verified_and_merged_users

Previously, if a GitHub API token failed authentication with a 401 error,
the GithubException was not caught, causing the server to crash.

Added try/except block to catch GithubException and log a warning instead
of crashing, allowing the server to continue with other valid tokens.
@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, conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • 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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 22, 2026

Walkthrough

Converted add_api_users_to_auto_verified_and_merged_users from synchronous to asynchronous, moved its initialization out of the constructor into process(), and implemented concurrent token validation using an async check_token helper with asyncio.gather. Tests updated to await the new async method.

Changes

Cohort / File(s) Summary
GitHub API Async Refactoring
webhook_server/libs/github_api.py
Made add_api_users_to_auto_verified_and_merged_users async; added internal async def check_token(api, token) to validate tokens (rate-limit check + fetch login) and run checks concurrently via asyncio.gather. Moved population of auto-verified users from __init__ to process() and adjusted blocking calls to use asyncio.to_thread where appropriate.
Test Updates
webhook_server/tests/test_github_api.py
Rewrote test_add_api_users_to_auto_verified_and_merged_users as an async test with @pytest.mark.asyncio; changed call to await gh.add_api_users_to_auto_verified_and_merged_users() and updated mock token source to use TEST_GITHUB_TOKEN.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/S

Suggested reviewers

  • dbasunag
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title focuses on error handling, but the actual changes implement async refactoring with concurrent token checks—a much larger architectural shift than error handling alone. Update title to reflect the primary change: something like 'refactor(github-api): convert add_api_users to async with concurrent token validation' to accurately represent the async/concurrency refactoring work.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/api-auth-error-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Jan 22, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive info exposure

Description: The new warning logs include a token-derived suffix (token_suffix from _token[-4:]) and
also interpolate the caught exception ({ex}), which can leak sensitive authentication
material or correlatable token fragments into logs (e.g., if logs are exposed to
lower-privileged operators or external log aggregation), so the token fragment and
exception content should be treated as potentially sensitive.
github_api.py [599-612]

Referred Code
token_suffix = f"...{_token[-4:]}" if _token else "unknown"
if _api.rate_limiting[-1] == 60:
    self.logger.warning(
        f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
        f"(token ending in '{token_suffix}'), skipping"
    )
    continue

try:
    _api_user = _api.get_user().login
except GithubException as ex:
    self.logger.warning(
        f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
    )
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Token data in logs: The new warning logs include token_suffix derived from the GitHub API token, which is
still secret material and should not be logged even partially.

Referred Code
token_suffix = f"...{_token[-4:]}" if _token else "unknown"
if _api.rate_limiting[-1] == 60:
    self.logger.warning(
        f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
        f"(token ending in '{token_suffix}'), skipping"
    )
    continue

try:
    _api_user = _api.get_user().login
except GithubException as ex:
    self.logger.warning(
        f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
    )

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Secret handling issue: The PR introduces handling that exposes part of a secret token (_token[-4:]) via logging,
which is insecure data handling for sensitive credentials.

Referred Code
token_suffix = f"...{_token[-4:]}" if _token else "unknown"
if _api.rate_limiting[-1] == 60:
    self.logger.warning(
        f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
        f"(token ending in '{token_suffix}'), skipping"

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Exception details logged: The warning log appends the raw GithubException ({ex}), which may include sensitive
request/response details depending on PyGithub configuration and should be
verified/redacted as needed.

Referred Code
except GithubException as ex:
    self.logger.warning(
        f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
    )

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Jan 22, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Offload blocking API call
Suggestion Impact:The method was converted to async and is now awaited from the async process() method. The blocking GitHub API calls were offloaded to a thread via asyncio.to_thread, including the suggested _api.get_user().login call (and additionally the rate_limiting check was also wrapped similarly). Logging behavior was also adjusted in the exception path.

code diff:

@@ -178,8 +178,8 @@
         # This prevents predictable paths and ensures isolation between concurrent webhook handlers
         self.clone_repo_dir: str = tempfile.mkdtemp(prefix=f"github-webhook-{self.repository_name}-")
         self._repo_cloned: bool = False  # Track if repository has been cloned
-        # Initialize auto-verified users from API users
-        self.add_api_users_to_auto_verified_and_merged_users()
+        # Note: auto-verified users from API users are initialized in process()
+        # because the method is async and requires asyncio.to_thread() for blocking calls
 
         self.current_pull_request_supported_retest = self._current_pull_request_supported_retest
         self.issue_url_for_welcome_msg: str = (
@@ -408,6 +408,9 @@
             raise RuntimeError(f"Repository clone failed: {ex}") from ex
 
     async def process(self) -> Any:
+        # Initialize auto-verified users from API users (async operation)
+        await self.add_api_users_to_auto_verified_and_merged_users()
+
         event_log: str = f"Event type: {self.github_event}. event ID: {self.x_github_delivery}"
 
         # Start webhook routing context step
@@ -593,11 +596,20 @@
             await self._update_context_metrics()
             return None
 
-    def add_api_users_to_auto_verified_and_merged_users(self) -> None:
+    async def add_api_users_to_auto_verified_and_merged_users(self) -> None:
         apis_and_tokens = get_apis_and_tokes_from_config(config=self.config)
         for _api, _token in apis_and_tokens:
             token_suffix = f"...{_token[-4:]}" if _token else "unknown"
-            if _api.rate_limiting[-1] == 60:
+            try:
+                rate_limit_remaining = await asyncio.to_thread(lambda api: api.rate_limiting[-1], _api)
+            except GithubException as ex:
+                self.logger.warning(
+                    f"{self.log_prefix} Failed to get API rate limit for token ending in '{token_suffix}', "
+                    f"skipping. {ex}"
+                )
+                continue
+
+            if rate_limit_remaining == 60:
                 self.logger.warning(
                     f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
                     f"(token ending in '{token_suffix}'), skipping"
@@ -605,9 +617,9 @@
                 continue
 
             try:
-                _api_user = _api.get_user().login
+                _api_user = await asyncio.to_thread(lambda api: api.get_user().login, _api)
             except GithubException as ex:
-                self.logger.warning(
+                self.logger.exception(
                     f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
                 )
                 continue

Convert add_api_users_to_auto_verified_and_merged_users to an async method and
use asyncio.to_thread to run the blocking _api.get_user().login call in a
separate thread, preventing it from blocking the event loop.

webhook_server/libs/github_api.py [596-615]

-def add_api_users_to_auto_verified_and_merged_users(self) -> None:
+async def add_api_users_to_auto_verified_and_merged_users(self) -> None:
     ...
     try:
-        _api_user = _api.get_user().login
+        _api_user = await asyncio.to_thread(lambda: _api.get_user().login)
     except GithubException as ex:
         self.logger.warning(
             f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
         )
         continue
     self.auto_verified_and_merged_users.append(_api_user)

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a blocking I/O call (_api.get_user().login) in an asynchronous context, which can severely impact application performance. Using asyncio.to_thread is the correct way to handle this, making it a critical performance fix.

High
General
Guard token slicing length

Add a length check before slicing the _token to avoid creating a misleading
token_suffix for tokens shorter than four characters.

webhook_server/libs/github_api.py [599]

-token_suffix = f"...{_token[-4:]}" if _token else "unknown"
+token_suffix = (
+    f"...{_token[-4:]}" if _token and len(_token) >= 4
+    else _token or "unknown"
+)
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: The suggestion correctly identifies that slicing a short token can lead to a misleading log message, although the claim about IndexError is incorrect for Python slicing. The proposed change improves logging clarity for edge cases.

Low
  • Update

@rnetser
Copy link
Copy Markdown
Collaborator Author

rnetser commented Jan 22, 2026

/build-and-push-container

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 22, 2026
@rnetser
Copy link
Copy Markdown
Collaborator Author

rnetser commented Jan 28, 2026

/build-and-push-container

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@webhook_server/libs/github_api.py`:
- Around line 607-615: Replace the use of self.logger.error in the except block
that catches GithubException when calling _api.get_user() with
self.logger.exception so the traceback is automatically recorded; keep the
existing message context (including self.log_prefix and token_suffix) and the
continue behavior, leaving the surrounding logic that appends _api_user to
self.auto_verified_and_merged_users unchanged.

Comment thread webhook_server/libs/github_api.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@webhook_server/libs/github_api.py`:
- Around line 596-615: In add_api_users_to_auto_verified_and_merged_users,
accessing _api.rate_limiting[-1] can raise GithubException for invalid tokens;
wrap the rate_limiting check in a try/except that catches GithubException (same
as the existing except used for _api.get_user()), log a clear message including
token_suffix and self.log_prefix, and continue to the next token on exception so
initialization doesn't crash; ensure you still perform the existing check when
rate_limiting is readable.

@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, conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@webhook_server/libs/github_api.py`:
- Around line 619-648: The current check_token coroutine only catches
GithubException which lets other exceptions (e.g., ConnectionError, OSError)
escape and cause asyncio.gather to cancel siblings; update check_token to catch
Exception (replace both "except GithubException as ex" clauses with "except
Exception as ex") so any error is logged and the function returns None,
preserving the intended graceful-skip behavior when called via asyncio.gather
over apis_and_tokens; alternatively you can pass return_exceptions=True to
asyncio.gather and filter non-str(str) results, but the preferred minimal change
is broadening the exception handlers inside check_token while keeping existing
log messages and return None semantics.
- Around line 640-644: The log call inside the GithubException handler
redundantly injects the exception object into the f-string; update the except
block handling GithubException so the self.logger.exception(...) call only
contains the descriptive message (e.g., using self.log_prefix and token_suffix)
and does not format in {ex}, leaving the exception and traceback to be attached
automatically; keep the current return None behavior and target the except block
that catches GithubException and calls self.logger.exception with
self.log_prefix and token_suffix.

…ithubException

asyncio.to_thread can raise ConnectionError, OSError, etc. in addition
to GithubException. Since asyncio.gather cancels sibling tasks on the
first unhandled exception, a network failure on one token would prevent
all other tokens from being validated. Broadening to Exception ensures
each token check is fully isolated.
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Feb 17, 2026

Code Review by Qodo

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

Grey Divider


Action required

1. Token suffix logged 📘 Rule violation ⛨ Security
Description
The new warning/exception logs include the last 4 characters of the GitHub API token, which is
secret material and should not appear in logs. This can leak credential fragments and violates
secure data handling requirements.
Code

webhook_server/libs/github_api.py[R621-643]

+            token_suffix = f"...{token[-4:]}" if token else "unknown"
+            try:
+                rate_limit_remaining = await asyncio.to_thread(lambda: api.rate_limiting[-1])
+            except GithubException as ex:
               self.logger.warning(
-                    f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token, skipping"
+                    f"{self.log_prefix} Failed to get API rate limit for token ending in '{token_suffix}', "
+                    f"skipping. {ex}"
               )
-                continue
+                return None
+
+            if rate_limit_remaining == 60:
+                self.logger.warning(
+                    f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
+                    f"(token ending in '{token_suffix}'), skipping"
+                )
+                return None
+
+            try:
+                _api_user = await asyncio.to_thread(lambda: api.get_user().login)
+            except GithubException as ex:
+                self.logger.exception(
+                    f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
+                )
Evidence
The compliance checklist prohibits secrets in logs and requires sensitive inputs (like tokens) to be
handled securely. The changed code constructs token_suffix from token[-4:] and writes it into
warning/exception log messages.

Rule 5: Generic: Secure Logging Practices
Rule 6: Generic: Security-First Input Validation and Data Handling
webhook_server/libs/github_api.py[621-643]

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 log messages include a token-derived suffix (`token[-4:]`), which is secret material and must not be logged.
## Issue Context
`add_api_users_to_auto_verified_and_merged_users()` logs `token_suffix` in both warning and exception paths. Even partial token values can aid attackers and violate secure logging/data-handling requirements.
## Fix Focus Areas
- webhook_server/libs/github_api.py[621-643]

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


2. Unhandled exceptions abort webhook 🐞 Bug ⛯ Reliability
Description
check_token() only catches GithubException and asyncio.gather() is used without
return_exceptions=True, so any other exception from to_thread/PyGithub will bubble up and abort
process() before routing the webhook. This undermines the goal of “skip bad token and continue” and
can drop entire webhook events.
Code

webhook_server/libs/github_api.py[R619-649]

+        async def check_token(api: github.Github, token: str) -> str | None:
+            """Check a single API token and return the user login if valid, None otherwise."""
+            token_suffix = f"...{token[-4:]}" if token else "unknown"
+            try:
+                rate_limit_remaining = await asyncio.to_thread(lambda: api.rate_limiting[-1])
+            except GithubException as ex:
               self.logger.warning(
-                    f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token, skipping"
+                    f"{self.log_prefix} Failed to get API rate limit for token ending in '{token_suffix}', "
+                    f"skipping. {ex}"
               )
-                continue
+                return None
+
+            if rate_limit_remaining == 60:
+                self.logger.warning(
+                    f"{self.log_prefix} API has rate limit set to 60 which indicates an invalid token "
+                    f"(token ending in '{token_suffix}'), skipping"
+                )
+                return None
+
+            try:
+                _api_user = await asyncio.to_thread(lambda: api.get_user().login)
+            except GithubException as ex:
+                self.logger.exception(
+                    f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
+                )
+                return None
+
+            return _api_user

-            self.auto_verified_and_merged_users.append(_api.get_user().login)
+        results = await asyncio.gather(*[check_token(api, token) for api, token in apis_and_tokens])
+        self.auto_verified_and_merged_users.extend(user for user in results if user is not None)
Evidence
process() awaits add_api_users_to_auto_verified_and_merged_users() at the beginning; if
add_api_users raises, webhook routing never starts and the background task is treated as an
unexpected error. Inside add_api_users, only GithubException is caught around the blocking calls,
and gather will re-raise the first unhandled exception.

webhook_server/libs/github_api.py[410-418]
webhook_server/libs/github_api.py[616-649]
webhook_server/app.py[455-487]

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

## Issue description
`add_api_users_to_auto_verified_and_merged_users()` is intended to skip invalid tokens and continue, but it only catches `GithubException` and uses `asyncio.gather()` without `return_exceptions=True`. Any other exception coming from the `asyncio.to_thread(...)` calls will bubble up, causing `process()` to fail before webhook routing.
### Issue Context
This runs at the very beginning of `process()`, so failures here prevent handling any webhook event.
### Fix Focus Areas
- webhook_server/libs/github_api.py[410-413]
- webhook_server/libs/github_api.py[619-649]
### Suggested approach
- Add `except Exception as ex:` in `check_token(...)` around both blocking calls (rate limiting + get_user) and return `None` after logging.
- Consider using `asyncio.gather(..., return_exceptions=True)` and filtering/logging exception objects so one task cannot fail the whole gather.
- Optionally wrap `await self.add_api_users_to_auto_verified_and_merged_users()` in `process()` with a broad try/except to keep routing even if token initialization fails entirely.

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



Remediation recommended

3. Unbounded concurrent token checks 🐞 Bug ➹ Performance
Description
The new asyncio.gather launches one task per configured token and each task uses two to_thread
calls, which can burst threads and GitHub API requests if many tokens are configured. This is a
regression from the previous sequential loop and can increase rate-limit spend and threadpool
contention.
Code

webhook_server/libs/github_api.py[R648-649]

+        results = await asyncio.gather(*[check_token(api, token) for api, token in apis_and_tokens])
+        self.auto_verified_and_merged_users.extend(user for user in results if user is not None)
Evidence
All configured tokens are turned into (api, token) pairs with no cap, and add_api_users schedules a
check for all of them simultaneously via gather. Each check does blocking work in the default
threadpool (to_thread).

webhook_server/utils/helpers.py[426-434]
webhook_server/libs/github_api.py[622-649]

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

## Issue description
`asyncio.gather(*[...])` schedules checks for all configured tokens at once; each check uses `asyncio.to_thread` twice. With many tokens this can create unnecessary parallelism, burst outbound API calls, and contend for the default threadpool.
### Issue Context
The previous implementation effectively checked tokens sequentially.
### Fix Focus Areas
- webhook_server/libs/github_api.py[619-649]
- webhook_server/utils/helpers.py[426-434]
### Suggested approach
- Add a small concurrency limit (e.g., `Semaphore(5)`), wrapping the body of `check_token`.
- Or replace `gather` with a simple `for` loop doing `await check_token(...)` to keep behavior predictable.
- If you keep gather, consider batching tokens (chunks) to cap parallelism.

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


4. Noisy exception logging 🐞 Bug ✓ Correctness
Description
Authentication/permission failures for a single token are handled by skipping the token, but the
code logs them with logger.exception(), emitting a full stack trace for an expected/handled
condition. This can spam logs and obscure real errors.
Code

webhook_server/libs/github_api.py[R640-644]

+            except GithubException as ex:
+                self.logger.exception(
+                    f"{self.log_prefix} Failed to get API user for token ending in '{token_suffix}', skipping. {ex}"
+                )
+                return None
Evidence
The code treats this GithubException as non-fatal (returns None) but logs it with logger.exception,
which records tracebacks and typically signals an error-level event.

webhook_server/libs/github_api.py[638-646]

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

## Issue description
Per-token auth failures are expected in the presence of stale/invalid tokens and are handled by skipping the token. Logging them with `logger.exception()` generates stack traces and can create excessive noise.
### Issue Context
The method explicitly continues to the next token (`return None`), indicating this is non-fatal.
### Fix Focus Areas
- webhook_server/libs/github_api.py[638-644]
### Suggested approach
- Replace `self.logger.exception(...)` with `self.logger.warning(...)` or `self.logger.info(...)`.
- If stack traces are occasionally useful, gate `exc_info=True` behind a debug-level check or only for non-auth-related exceptions.

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/libs/github_api.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webhook_server/libs/github_api.py`:
- Around line 640-644: The log message in the except block for get API user is
redundantly interpolating the exception into logger.exception; remove the
explicit "{ex}" from the formatted string so logger.exception(self.log_prefix
... token_suffix) is called with only the contextual message (it will
automatically include the exception and traceback), e.g., update the call to
logger.exception(...) in the except block that references self.log_prefix and
token_suffix and keep the subsequent return None unchanged.

Comment thread webhook_server/libs/github_api.py
@rnetser
Copy link
Copy Markdown
Collaborator Author

rnetser commented Feb 18, 2026

/build-and-push-container

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

@rnetser
Copy link
Copy Markdown
Collaborator Author

rnetser commented Feb 18, 2026

/verified

@myakove
Copy link
Copy Markdown
Collaborator

myakove commented Feb 19, 2026

/lgtm
/approve

@myakove-bot
Copy link
Copy Markdown
Collaborator

Successfully removed PR tag: ghcr.io/myk-org/github-webhook-server:pr-984.

@myakove-bot
Copy link
Copy Markdown
Collaborator

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

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.

3 participants