Skip to content

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

Open
rnetser wants to merge 7 commits intomainfrom
fix/api-auth-error-handling
Open

fix(github-api): handle authentication errors in add_api_users_to_auto_verified_and_merged_users#984
rnetser wants to merge 7 commits intomainfrom
fix/api-auth-error-handling

Conversation

@rnetser
Copy link
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

  • Bug Fixes

    • Prevents crashes when fetching GitHub user info by catching and skipping failed API calls.
  • Improvements

    • Auto-verified users are now populated asynchronously during processing to avoid blocking initialization.
    • Concurrent credential validation, better handling of rate limits, and clearer logs that include credential suffixes.
  • Tests

    • Updated test to run asynchronously to match the new async initialization flow.

✏️ Tip: You can customize this high-level summary in your review settings.

…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
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

coderabbitai bot commented Jan 22, 2026

Walkthrough

process() now awaits the moved initializer add_api_users_to_auto_verified_and_merged_users(), which asynchronously checks each (api, token) pair via asyncio.to_thread for rate limits and API user, logs token-scoped warnings on errors or low quota, and appends valid API user logins to auto-verified lists.

Changes

Cohort / File(s) Summary
GitHub API core
webhook_server/libs/github_api.py
add_api_users_to_auto_verified_and_merged_users changed from sync to async def and is now awaited from process(). Per-token logic moved into an async check using asyncio.to_thread to read rate_limit and fetch _api.get_user(), handles GithubException with token-suffix in logs, skips tokens with rate_limit.remaining == 60, and aggregates non-None logins into auto_verified_and_merged_users.
Tests
webhook_server/tests/test_github_api.py
Test test_add_api_users_to_auto_verified_and_merged_users converted to async def and decorated for asyncio; invocation updated to await gh.add_api_users_to_auto_verified_and_merged_users() and token references aligned to test constant.

Sequence Diagram(s)

mermaid
sequenceDiagram
rect rgba(200,200,255,0.5)
Participant Process
Participant AsyncWorker
Participant GitHubAPI
Participant Storage
end
Process->>AsyncWorker: await add_api_users_to_auto_verified_and_merged_users()
AsyncWorker-->>GitHubAPI: asyncio.to_thread(rate_limit)
alt rate limit error
GitHubAPI-->>AsyncWorker: GithubException (logged, skip)
else sufficient/low quota
GitHubAPI-->>AsyncWorker: rate_limit.remaining
AsyncWorker->>GitHubAPI: asyncio.to_thread(get_user)
alt get_user error
GitHubAPI-->>AsyncWorker: GithubException (logged, skip)
else success
GitHubAPI-->>AsyncWorker: user.login
AsyncWorker->>Storage: append user.login to auto_verified_and_merged_users
end
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title claims to only handle authentication errors, but the actual changeset fundamentally refactors the method to be asynchronous with concurrent token processing via asyncio.gather—a much broader architectural change than the title suggests. Update the title to reflect the primary change: 'refactor(github-api): convert add_api_users_to_auto_verified_and_merged_users to async with concurrent token validation' or similar, ensuring it captures both the async conversion and concurrent processing pattern.
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

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

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
Collaborator Author

rnetser commented Jan 22, 2026

/build-and-push-container

@myakove-bot
Copy link
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
Collaborator Author

rnetser commented Jan 28, 2026

/build-and-push-container

@myakove-bot
Copy link
Collaborator

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

Copy link

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

Copy link

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

Address CodeRabbit review comment: wrap _api.rate_limiting[-1]
access in try/except to handle GithubException on invalid tokens.
Also fix logger.exception to logger.warning for non-exception context.
Copy link

@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-624: The method
add_api_users_to_auto_verified_and_merged_users performs blocking PyGithub calls
(_api.rate_limiting and _api.get_user()) and must be converted to an async
initialization or use asyncio.to_thread wrappers: make
add_api_users_to_auto_verified_and_merged_users async (or add an async variant
called from process()), and for each (_api, _token) call await
asyncio.to_thread(lambda: _api.rate_limiting) and await
asyncio.to_thread(lambda: _api.get_user()) (or equivalent callables) before
appending to self.auto_verified_and_merged_users; ensure any callers invoke the
new async method from an async context (or run it once during startup) so the
event loop is not blocked.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 29, 2026
Address CodeRabbit review: convert blocking PyGithub calls
(_api.rate_limiting and _api.get_user()) to async using
asyncio.to_thread() to prevent blocking the event loop.

- Convert add_api_users_to_auto_verified_and_merged_users to async
- Move call from __init__ to process() (async context)
- Wrap blocking PyGithub calls with asyncio.to_thread()
- Update test to handle async method
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webhook_server/tests/test_github_api.py (1)

624-694: LOW: Use TEST_GITHUB_TOKEN in this test.

This avoids accidental drift and keeps all test tokens consistent and centrally managed. As per coding guidelines: Test files should use AsyncMock, Mock, and patch for mocking - use TEST_GITHUB_TOKEN constant for test tokens.

✅ Suggested fix
-        mock_get_api.return_value = (Mock(), "token", "apiuser")
+        mock_get_api.return_value = (Mock(), TEST_GITHUB_TOKEN, "apiuser")
@@
-        mock_get_apis.return_value = [(mock_api, "token")]
+        mock_get_apis.return_value = [(mock_api, TEST_GITHUB_TOKEN)]
🤖 Fix all issues with AI agents
In `@webhook_server/libs/github_api.py`:
- Around line 599-627: The function
add_api_users_to_auto_verified_and_merged_users currently iterates
apis_and_tokens sequentially; refactor so each (_api, _token) is processed
concurrently via asyncio.gather over an async helper coroutine that (1) computes
token_suffix, (2) uses await asyncio.to_thread(...) to read api.rate_limiting
and api.get_user().login, (3) applies the same checks on rate_limit_remaining ==
60 and logs warnings/exceptions (using the same log messages and
self.log_prefix), and (4) returns the _api_user or None; after gather, extend
self.auto_verified_and_merged_users with non-None results while preserving
per-token skip behavior and exception logging.

Address CodeRabbit review comments:
- Refactor add_api_users_to_auto_verified_and_merged_users to process
  all tokens concurrently using asyncio.gather instead of sequential loop
- Use TEST_GITHUB_TOKEN constant in test for consistency
@rnetser
Copy link
Collaborator Author

rnetser commented Jan 29, 2026

Regarding the outside-diff-range comment about using TEST_GITHUB_TOKEN in webhook_server/tests/test_github_api.py:

Done - updated test to use TEST_GITHUB_TOKEN constant.

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.

2 participants