Skip to content

Conversation

@myakove
Copy link
Collaborator

@myakove myakove commented Jan 5, 2026

Summary

This PR migrates the webhook server from traditional line-based logging to structured JSON logging with comprehensive context tracking and workflow step recording.

Key Features

  • ContextVar-based WebhookContext: Async-safe context propagation across handler methods
  • Structured JSON log writer: Single JSON entry per webhook lifecycle with complete workflow tracking
  • Workflow step tracking: All handlers record their processing steps via context
  • Human-readable durations: Automatic formatting of time durations in logs (e.g., "1.23s", "45.67ms")
  • Comprehensive test coverage: 99 new tests added, maintaining 91.04% overall coverage

Architecture Changes

New Components

  1. webhook_server/libs/context.py

    • WebhookContext dataclass for storing webhook metadata and workflow steps
    • webhook_context ContextVar for async-safe context propagation
    • Context manager for automatic context cleanup
  2. webhook_server/libs/structured_logger.py

    • StructuredLogger class for writing JSON log entries
    • Thread-safe file locking for concurrent webhook processing
    • Automatic duration calculation and formatting
    • Pretty-printed JSON with sorted keys for readability

Updated Components

  1. webhook_server/app.py

    • Context creation at webhook processing start
    • Structured log writing after handler completion
    • Integration with existing error handling
  2. All handlers (pull_request, issue_comment, check_run, etc.)

    • Added context step tracking via context.add_step()
    • Removed direct logging calls (logger.step, logger.success)
    • Cleaner code focused on business logic
  3. webhook_server/libs/log_parser.py & webhook_server/web/log_viewer.py

    • Support for parsing both legacy line-based and new JSON logs
    • Streaming JSON parser for memory-efficient log processing
    • Backward compatibility with existing logs

Removed Components

  • logger.step() custom logging method
  • logger.success() custom logging method
  • format_task_fields() helper function
  • Manual logging calls in handlers

Benefits

  1. Structured data: Machine-readable JSON format for better log analysis
  2. Complete workflow tracking: Single JSON entry contains entire webhook processing workflow
  3. Async-safe: ContextVar ensures correct context in concurrent webhook processing
  4. Better debuggability: All steps recorded with accurate timestamps and durations
  5. Backward compatible: Legacy logs still supported, gradual migration possible
  6. Cleaner code: Handlers focus on business logic, not logging

Example Log Entry

{
  "context": {
    "delivery_id": "12345678-1234-1234-1234-123456789abc",
    "duration": "1.23s",
    "event": "pull_request",
    "event_data": {...},
    "owner": "myk-org",
    "pr_number": 123,
    "repository": "github-webhook-server",
    "sha": "abc123...",
    "start_time": "2026-01-05T12:34:56.789012",
    "steps": [
      {
        "duration": "0.12s",
        "step": "Fetching PR data",
        "timestamp": "2026-01-05T12:34:56.901234"
      },
      {
        "duration": "0.45s",
        "step": "Running auto-merge checks",
        "timestamp": "2026-01-05T12:34:57.112345"
      }
    ]
  },
  "level": "info",
  "message": "Webhook processing completed",
  "timestamp": "2026-01-05T12:34:57.901234"
}

Testing

All 1058 tests pass with 91.04% coverage:

  • 99 new tests for structured logging system
  • Integration tests for context tracking
  • Unit tests for log parsing and formatting
  • Handler tests updated for context usage

Migration Notes

Backward compatibility maintained:

  • Legacy line-based logs continue to be parsed correctly
  • Log viewer supports both formats seamlessly
  • No breaking changes to existing functionality

Deployment:

  • No configuration changes required
  • Logs automatically written in new format
  • Legacy logs remain accessible

Test Plan

  • All existing tests pass (1058 tests)
  • New structured logging tests pass (99 tests)
  • Code coverage maintained at 91.04%
  • Manual testing of webhook processing
  • Log viewer compatibility verified
  • Context propagation tested with concurrent webhooks

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Structured JSON webhook logs with per-webhook context, step timelines, and JSON-first retrieval in the log viewer.
  • Improvements

    • Async, streaming log viewer for faster, memory- and IO-efficient access.
    • Context-aware, lower-verbosity logging with concise webhook summaries and richer error capture (tracebacks, structured error fields).
    • JSON log parsing surfaced to UI/workflows.
  • Removals

    • Legacy log-field formatting helper removed.
  • Documentation

    • Detailed docs and examples for structured webhook logging.
  • Tests

    • Expanded async tests covering context, logging, JSON parsing, viewer, and streaming.
  • Chores

    • Added async file I/O dependency for streaming.

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

Migrate from text-based logging with regex parsing to structured JSON logging.

New components:
- webhook_server/utils/context.py: ContextVar-based WebhookContext for request tracking
- webhook_server/utils/structured_logger.py: JSON log writer with structured output
- webhook_server/tests/test_context.py: Tests for context system
- webhook_server/tests/test_structured_logger.py: Tests for structured logger
- webhook_server/tests/test_log_viewer.py: Tests for log viewer

Modified components:
- app.py: Integrate WebhookContext and structured logging
- github_api.py: Use structured logger throughout
- All handlers: Migrated to structured logging
- log_parser.py: Updated to parse JSON logs
- log_viewer.py: Enhanced to handle structured logs
- app_utils.py, helpers.py: Logging improvements
- All test files: Updated for structured logging

Benefits:
- Eliminates regex-based log parsing
- Improved log searchability and filtering
- Better structured data extraction
- Simplified log analysis
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

Warning

Rate limit exceeded

@myakove has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a00931 and eae223f.

📒 Files selected for processing (1)
  • CLAUDE.md

Walkthrough

Creates a ContextVar-backed WebhookContext and JSONL structured logging; integrates ctx into GitHub API and handlers to track per-step lifecycle and errors; converts log viewer to async/JSON-first streaming; app ensures summary/log write and context clearing for every webhook run.

Changes

Cohort / File(s) Summary
Context infrastructure
webhook_server/utils/context.py
New WebhookContext dataclass and ContextVar API: create_context(), get_context(), clear_context(); per-step methods start_step / complete_step / fail_step and to_dict().
Structured logging writer
webhook_server/utils/structured_logger.py
New StructuredLogWriter and write_webhook_log(); atomic daily JSONL writes, optional POSIX locking, and write_error_log() for minimal error entries.
App utilities
webhook_server/utils/app_utils.py
New helpers: format_duration(), log_webhook_summary(), and get_workflow_steps_core() used by app/log viewer.
Webhook app entry point
webhook_server/app.py
Creates per-run ctx for background tasks; richer error-to-ctx propagation (tracebacks, network errors, CancelledError handling); always emits summary and attempts write_webhook_log(ctx) in finally; calls public get_workflow_steps_core().
Github wiring
webhook_server/libs/github_api.py
GithubWebhook carries ctx; PR metadata, token metrics, and repo_clone lifecycle recorded into ctx; failures capture tracebacks and update ctx steps.
Handlers (ctx wiring & logging simplification)
webhook_server/libs/handlers/*
check_run_handler.py, issue_comment_handler.py, labels_handler.py, owners_files_handler.py, pull_request_handler.py, pull_request_review_handler.py, push_handler.py, runner_handler.py
Handlers obtain optional ctx from github_webhook.ctx; many logger.step() / format_task_fields usages removed and replaced with ctx.start/complete/fail() or simplified logs; some try/excepts adapted to propagate CancelledError.
Log parsing & viewer
webhook_server/libs/log_parser.py, webhook_server/web/log_viewer.py
Added JSON parsing and JSON-first streaming (_stream_json_log_entries), get_workflow_steps_json(); log viewer APIs made async and prefer JSON logs with fallback to text parsing; streaming/export updated to async.
Helpers cleanup
webhook_server/utils/helpers.py
Removed format_task_fields(); callers updated to simpler logging or ctx-driven steps.
App-level workflow lookup
webhook_server/app.py, webhook_server/utils/app_utils.py
Removed internal _get_workflow_steps_core; uses public get_workflow_steps_core(controller, hook_id).
Tests: new/updated
webhook_server/tests/*
New tests for context and structured logger; many tests set mock_webhook.ctx = None; removed assertions relying on legacy logger.step(); added JSON parsing/viewer tests; many log-viewer tests converted to async.
Docs & misc
README.md, CLAUDE.md, .gitignore
Added Structured Webhook Logs docs and examples; CLAUDE.md now tracked (removed from .gitignore).
Deps / config
pyproject.toml, .pre-commit-config.yaml
Added aiofiles dependency and typing stubs; mypy hook updated to include types-aiofiles.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Receiver as Webhook Receiver
participant App as webhook_server.app
participant Context as WebhookContext (ContextVar)
participant GithubAPI as libs/github_api
participant Handler as Handler (PR/Check/Push/etc.)
participant Writer as StructuredLogWriter
participant FS as Filesystem

Note over App,Context: Webhook run lifecycle
Receiver->>App: enqueue background task (payload)
App->>Context: create_context(hook_id,event_type,repo...)
App->>GithubAPI: dispatch event (github_webhook with ctx)
GithubAPI->>Handler: invoke handler (ctx available)
Handler->>Context: ctx.start_step("handler_name")
alt success
    Handler-->>Context: ctx.complete_step("handler_name", metadata)
else failure
    Handler-->>Context: ctx.fail_step("handler_name", exception, traceback)
end
GithubAPI->>Context: _update_context_metrics() (token/rate data)
App->>Context: set completed_at and duration
App->>Writer: write_webhook_log(ctx)
Writer->>FS: atomic append to webhooks_YYYY-MM-DD.json
FS-->>Writer: write OK
App->>Context: clear_context()
Note over Writer,FS: Log files consumed by LogViewerController (JSON-first)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • rnetser
  • dbasunag

CRITICAL: Verify ContextVar isolation across concurrent async tasks to prevent cross-request context leakage.
HIGH: Audit StructuredLogWriter atomic write and fcntl fallback for cross-platform safety and concurrent-writer correctness.
MEDIUM: Confirm removal of format_task_fields() does not break downstream consumers or tooling that parsed those formatted fields.
LOW: Consider adding a JSONL schema/version field and migration notes for external log consumers.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: migration from line-based to structured JSON logging with context tracking, which aligns with the comprehensive changeset.
Docstring Coverage ✅ Passed Docstring coverage is 88.25% which is sufficient. The required threshold is 80.00%.

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.

@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: A tracking issue is created for this PR and will be closed when the PR is merged or closed
  • 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

📋 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)

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, or 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

@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: 12

Fix all issues with AI Agents 🤖
In @webhook_server/libs/handlers/owners_files_handler.py:
- Around line 17-18: The list_changed_files function currently swallows failures
and asyncio.CancelledError by using a broad except and returning an empty list;
change it to let CancelledError propagate (don’t catch asyncio.CancelledError),
and on any git run_command failure (success is False) or unexpected exception,
raise a clear exception instead of logging and returning [] so callers/CI fail
fast; use run_command’s result to include detailed error info in the raised
exception or log (e.g., include stdout/stderr/return code) and only catch and
re-raise non-cancellation exceptions if you need to add context.

In @webhook_server/libs/handlers/pull_request_review_handler.py:
- Around line 9-18: The class uses WebhookContext in a runtime-evaluated
annotation and __init__ lacks a return type; update
PullRequestReviewHandler.__init__ to annotate the ctx field with a string
literal (e.g., self.ctx: "WebhookContext" | None = ...) or enable postponed
evaluation via from __future__ import annotations, and add the explicit return
annotation -> None on __init__; optionally wrap the body of
process_pull_request_review_webhook_data in a try/finally so
complete_step("pr_review_handler") always runs when self.ctx exists.

In @webhook_server/libs/handlers/push_handler.py:
- Around line 29-55: process_push_webhook_data currently always calls
self.ctx.complete_step("push_handler") even when upload_to_pypi or
runner_handler.run_build_container raise; update the exception handlers so they
call self.ctx.fail_step("push_handler", ex, traceback) (or a new secondary step
like "push_handler.pypi" / "push_handler.container" with start/complete/fail
calls) instead of swallowing errors—specifically modify the except blocks around
await self.upload_to_pypi(...) and await
self.runner_handler.run_build_container(...) to capture the exception and
traceback and call ctx.fail_step with those details (or create and manage
per-operation steps) before re-raising or returning, so the JSON workflow
reflects failures accurately.

In @webhook_server/libs/log_parser.py:
- Around line 400-422: Change the two exception handlers in parse_json_log_file
to call self.logger.exception(...) instead of self.logger.error(...);
specifically replace the self.logger.error(f"Failed to read JSON log file
{file_path}: {e}") in the OSError except block and the
self.logger.error(f"Failed to decode JSON log file {file_path}: {e}") in the
UnicodeDecodeError except block with self.logger.exception(...) calls that
include the same descriptive message so the traceback is automatically logged.

In @webhook_server/tests/test_context.py:
- Around line 19-32: Add a concrete return type to the inner mock function so
mypy strict accepts it: change the signature of mock_now to include parameter
and return types (e.g. def mock_now(tz: Optional[datetime.tzinfo] = None) ->
datetime:) and import Optional and tzinfo/ datetime as needed; keep the function
body unchanged and ensure the patched target
(patch("webhook_server.utils.context.datetime")) still uses mock_now as before.

In @webhook_server/tests/test_github_api.py:
- Around line 1498-1500: Reuse the existing info_calls list instead of creating
a new info_completion_calls: remove the redundant assignment that builds
info_completion_calls from mock_logger.info.call_args_list and use the
previously created info_calls variable in the assertion that checks for
"deletion event (skipped)". Update the assertion to iterate over info_calls
(e.g., assert any("deletion event (skipped)" in call.lower() for call in
info_calls)) so the test uses the single shared collection of info log call
strings.

In @webhook_server/tests/test_log_parser.py:
- Around line 1256-1367: Test helpers use naive datetimes, missing type hints,
missing return type, and perform blocking I/O; update all datetime.datetime(...)
calls in these tests to include tzinfo=datetime.UTC, add annotations to the
collect_entries helper (async_gen: AsyncIterator[LogEntry], max_entries: int)
and its return type -> None, and replace the blocking file append using
open(...) with an asyncio.to_thread call (e.g., offload current_log.write_text
or the append operation) so monitor_log_directory and collect_entries operate
with UTC-aware timestamps, proper AsyncIterator typing, explicit None return,
and non-blocking file writes.

In @webhook_server/tests/test_log_viewer.py:
- Around line 18-31: The test test_stream_json_log_entries_no_log_directory has
an unused tmp_path parameter causing ARG002; to fix, make a trivial reference to
tmp_path (e.g., assert tmp_path is not None or add a no-op comment like
"tmp_path  # noqa: ARG002") inside that test so the parameter is used and
linting passes, keeping the rest of the test logic unchanged.

In @webhook_server/tests/test_structured_logger.py:
- Around line 15-405: The lint issues are minor: replace positional boolean
patches with explicit keyword usage (e.g., in test_write_log_without_fcntl and
test_write_log_uses_file_locking change @patch("...HAS_FCNTL", False) to use
explicit new= parameter) and mark the unused tmp_path in
test_different_dates_create_different_files as intentionally used (e.g., assign
_ = tmp_path) so the fixture name remains but Ruff won’t flag ARG002; locate
these in the tests named test_write_log_without_fcntl,
test_write_log_uses_file_locking, and
test_different_dates_create_different_files and apply the two small edits.

In @webhook_server/utils/structured_logger.py:
- Around line 92-117: StructuredLogWriter.write_log currently always uses
datetime.now(UTC) for completed_at which can drift from
WebhookContext.completed_at set elsewhere; change write_log to prefer
context.completed_at (from WebhookContext) as the source of truth and only fall
back to datetime.now(UTC) if context.completed_at is None, then compute
duration_ms using that chosen completed_at and context.started_at, and write
those values into context_dict["timing"] before serializing; update references
to context.to_dict(), WebhookContext.completed_at, write_webhook_log, and
log_webhook_summary to ensure consistency without mutating the original context.

In @webhook_server/web/log_viewer.py:
- Around line 814-857: The current _stream_json_log_entries reads each file with
readlines(), which loads the entire file into memory; change it to stream lines
into a bounded collections.deque sized by the remaining entries needed
(remaining = max_entries - total_yielded) while iterating the file line-by-line,
then iterate reversed(deque) to process newest-first and call
self.log_parser.get_raw_json_entry on each line; update total_yielded and break
out when reaching max_entries, and keep the existing exception handling around
the file open/processing loop.

Caution

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

⚠️ Outside diff range comments (3)
webhook_server/libs/handlers/issue_comment_handler.py (1)

41-63: Context step tracking is hooked in correctly; consider marking failures explicitly

Severity: LOW.
Wiring self.ctx from github_webhook.ctx and bracketing process_comment_webhook_data with start_step/complete_step("issue_comment_handler") (including the edited/deleted and welcome-message early exits) is consistent with the WebhookContext API and avoids leaking an “open” step. If you want failures in user_commands to be visible at the workflow level, a follow-up enhancement could be to call ctx.fail_step("issue_comment_handler", ...) when any gathered command raises instead of always marking the step as completed, but that’s optional and doesn’t block this PR.

Also applies to: 65-85, 114-116

webhook_server/app.py (1)

378-455: HIGH: Re-raise asyncio.CancelledError explicitly in process_with_error_handling

The inner async function catches cancellation signals via the broad except Exception as ex: handler at line 424. This violates the repo guideline: "Always re-raise asyncio.CancelledError - never suppress it in async Python exception handlers."

In Python 3.10 and earlier, CancelledError inherits from Exception, so it gets caught and logged as a generic failure instead of propagating cancellation. In 3.11+, it's a BaseException subclass, so it passes through—but explicit handling prevents confusion and ensures correct shutdown semantics across versions.

Add an explicit except asyncio.CancelledError: handler before the generic Exception catch, log it (optional), and re-raise:

Example fix
        except RepositoryNotFoundInConfigError:
            # Repository-specific error - not exceptional, log as error not exception
            _logger.error(f"{_log_context} Repository not found in configuration")
            ctx.success = False
            ctx.error = {
                "type": "RepositoryNotFoundInConfigError",
                "message": "Repository not found in configuration",
                "traceback": "",
            }
        except asyncio.CancelledError:
            # Preserve cooperative cancellation semantics
            _logger.info(f"{_log_context} Webhook processing cancelled")
            raise
        except (httpx.ConnectError, httpx.RequestError, requests.exceptions.ConnectionError) as ex:
            # Network/connection errors - can be transient
            _logger.exception(f"{_log_context} API connection error - check network connectivity")
            ctx.success = False
            ctx.error = {
                "type": type(ex).__name__,
                "message": str(ex),
                "traceback": traceback.format_exc(),
            }
        except Exception as ex:
            # Catch-all for unexpected errors
            _logger.exception(f"{_log_context} Unexpected error in background webhook processing")
            ctx.success = False
            ctx.error = {
                "type": type(ex).__name__,
                "message": str(ex),
                "traceback": traceback.format_exc(),
            }
webhook_server/libs/handlers/pull_request_handler.py (1)

805-907: HIGH: Re-raise asyncio.CancelledError in check_if_can_be_merged to preserve task cancellation semantics

The broad except Exception as ex at line 897 will swallow asyncio.CancelledError, violating the explicit repo guideline that cancellation must never be suppressed in async handlers. Suppressing CancelledError breaks cooperative cancellation semantics and prevents the framework from cleanly shutting down tasks—this can mask resource leaks and cause hung connections.

Separate the CancelledError handler (re-raise immediately after optional context logging) from the generic exception handler (log, cleanup, and record failure context). Also switch to logger.exception() to capture the traceback automatically.

Corrected error handling pattern
         try:
             self.logger.info(f"{self.log_prefix} Check if {CAN_BE_MERGED_STR}.")
             await self.check_run_handler.set_merge_check_in_progress()
             # ... existing logic ...
-        except Exception as ex:
-            self.logger.error(
-                f"{self.log_prefix} Failed to check if can be merged, set check run to {FAILURE_STR} {ex}"
-            )
-            _err = "Failed to check if can be merged, check logs"
-            output["text"] = _err
-            await self.labels_handler._remove_label(pull_request=pull_request, label=CAN_BE_MERGED_STR)
-            await self.check_run_handler.set_merge_check_failure(output=output)
-            if self.ctx:
-                self.ctx.fail_step("check_merge_eligibility", ex, traceback.format_exc())
+        except asyncio.CancelledError:
+            if self.ctx:
+                self.ctx.fail_step(
+                    "check_merge_eligibility",
+                    asyncio.CancelledError(),
+                    traceback.format_exc(),
+                )
+            raise
+        except Exception as ex:
+            self.logger.exception(
+                f"{self.log_prefix} Failed to check if can be merged, set check run to {FAILURE_STR}"
+            )
+            _err = "Failed to check if can be merged, check logs"
+            output["text"] = _err
+            await self.labels_handler._remove_label(pull_request=pull_request, label=CAN_BE_MERGED_STR)
+            await self.check_run_handler.set_merge_check_failure(output=output)
+            if self.ctx:
+                self.ctx.fail_step("check_merge_eligibility", ex, traceback.format_exc())

(asyncio and traceback are already imported.)

Enhance structured logging with pretty-printed JSON format and improve
log viewer file ordering for better user experience.

Changes:
- structured_logger.py: Pretty-print JSON logs with 2-space indentation
  and blank line separators between entries for improved readability
- log_viewer.py: Prioritize JSON webhook files over internal logs in
  file listing to show primary data source first
Updated test_structured_logger.py to handle pretty-printed JSON format
with blank line separators instead of JSONL. Fixed test_log_api.py to
properly mock file operations and stat() method for error handling test.
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: 0

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_log_api.py (1)

443-470: MEDIUM: Test name no longer reflects the actual failure mode being tested.

The test test_stream_log_entries_parse_error now simulates an open() failure rather than a parse error. The name is misleading since the test exercises I/O exception handling, not JSON/log parsing failures.

Consider renaming to test_stream_log_entries_file_read_error or test_stream_log_entries_io_error to accurately describe the failure path being tested.

Suggested rename
-    def test_stream_log_entries_parse_error(self, controller):
-        """Test log entries loading with parse error."""
+    def test_stream_log_entries_file_read_error(self, controller):
+        """Test log entries loading with file read error."""
♻️ Duplicate comments (3)
webhook_server/tests/test_structured_logger.py (1)

15-672: Comprehensive test coverage for structured logging.

The test suite thoroughly covers StructuredLogWriter and write_webhook_log:

  • Initialization and log directory creation
  • Date-based log file paths
  • JSON serialization with timing, workflow steps, PR details, and errors
  • File locking behavior on platforms with/without fcntl
  • Error handling and graceful recovery
  • Unicode content handling
  • Temp file cleanup scenarios

The previous review already noted the minor lint issues (boolean positional in @patch and unused tmp_path fixture). Those are cosmetic and don't affect test correctness.

webhook_server/web/log_viewer.py (1)

813-855: Memory concern with readlines() already noted in prior review.

The readlines() call at line 845 loads the entire file into memory before reversing, which can spike memory for large JSON log files. This was flagged in a previous review - consider using a bounded deque similar to _stream_log_entries to maintain consistent memory-efficient streaming behavior.

Memory-efficient alternative
             try:
                 with open(log_file, encoding="utf-8") as f:
-                    # Read lines in reverse for newest-first ordering
-                    lines = f.readlines()
-                    for line in reversed(lines):
+                    remaining_capacity = max_entries - total_yielded
+                    buffer: deque[dict[str, Any]] = deque(maxlen=remaining_capacity)
+                    
+                    for line in f:
                         if total_yielded >= max_entries:
                             break
-
                         data = self.log_parser.get_raw_json_entry(line)
                         if data:
-                            yield data
-                            total_yielded += 1
+                            buffer.append(data)
+                    
+                    for data in reversed(buffer):
+                        if total_yielded >= max_entries:
+                            break
+                        yield data
+                        total_yielded += 1
webhook_server/utils/structured_logger.py (1)

93-115: Timing source-of-truth concern previously noted.

The implementation always calculates completed_at locally at line 107, even if context.completed_at was already set by the caller. This can cause minor timing drift between the JSON log and other logging outputs. The previous review suggested preferring context.completed_at when set.

This is LOW severity since the drift is typically milliseconds, but worth addressing for consistency.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 268a8dc and 5b5a631.

📒 Files selected for processing (4)
  • webhook_server/tests/test_log_api.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/web/log_viewer.py
🧰 Additional context used
📓 Path-based instructions (4)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to __init__() - required parameters are always provided in this self-contained server application
Defensive checks ARE acceptable in destructors (__del__) because they may be called during failed initialization
Defensive checks ARE acceptable for lazy initialization where attributes explicitly start as None - check is legitimate for uninitialized attributes
Never use defensive checks on known library versions controlled in pyproject.toml - e.g., PyGithub >=2.4.0 methods are guaranteed to exist
Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., user.node_id, user.type, sender always exist in webhook payloads
Never use hasattr() for type discrimination - use isinstance() instead for type checking
Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging
ALL PyGithub method calls MUST be wrapped with asyncio.to_thread() to avoid blocking the FastAPI event loop - including .get_*(), .create_*(), .edit(), .add_to_*(), .remove_from_*()
ALL PyGithub property accesses that may trigger API calls MUST be wrapped with asyncio.to_thread() - properties like .draft, .mergeable, .state, .committer, .permissions, .labels, .assignees are NOT safe to access directly
PyGithub PaginatedList iteration MUST be wrapped in asyncio.to_thread() with list conversion - never iterate directly to avoid blocking the event loop
ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports
ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated
Code coverage of 90% or higher is ...

Files:

  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/web/log_viewer.py
webhook_server/**/*.{py,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Defensive checks ARE acceptable for optional parameters explicitly typed as Type | None - check is legitimate for parameters allowing None

Files:

  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/web/log_viewer.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test files MUST use TEST_GITHUB_TOKEN = 'ghp_test1234...' with # pragma: allowlist secret comment to avoid security warnings in test tokens

Files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_api.py
webhook_server/web/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

FastAPI endpoint handlers must be async (async def) to maintain non-blocking operation of the event loop

Files:

  • webhook_server/web/log_viewer.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification to ensure compatibility with GitHub API changes
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation

Applied to files:

  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely without backward compatibility requirements - return types, method signatures, and implementations may be modified without deprecation warnings

Applied to files:

  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/web/log_viewer.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/tests/**/*.py : Test files MUST use `TEST_GITHUB_TOKEN = 'ghp_test1234...'` with `# pragma: allowlist secret` comment to avoid security warnings in test tokens

Applied to files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Code coverage of 90% or higher is MANDATORY - measured via `pytest --cov=webhook_server` and enforced in CI

Applied to files:

  • webhook_server/tests/test_structured_logger.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Eliminate unnecessary defensive checks for return type changes and method signature modifications in internal APIs - fail-fast principle is preferred over compatibility

Applied to files:

  • webhook_server/tests/test_log_api.py
  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging

Applied to files:

  • webhook_server/tests/test_log_api.py
🧬 Code graph analysis (4)
webhook_server/utils/structured_logger.py (2)
webhook_server/libs/config.py (1)
  • Config (11-142)
webhook_server/utils/context.py (3)
  • WebhookContext (49-232)
  • get_context (274-284)
  • to_dict (195-232)
webhook_server/tests/test_structured_logger.py (3)
webhook_server/libs/config.py (2)
  • Config (11-142)
  • exists (24-26)
webhook_server/utils/context.py (4)
  • WebhookContext (49-232)
  • start_step (116-133)
  • complete_step (135-157)
  • to_dict (195-232)
webhook_server/utils/structured_logger.py (5)
  • StructuredLogWriter (53-263)
  • write_webhook_log (266-290)
  • _get_log_file_path (79-91)
  • write_log (93-179)
  • write_error_log (181-263)
webhook_server/tests/test_log_api.py (1)
webhook_server/web/log_viewer.py (1)
  • _stream_log_entries (734-811)
webhook_server/web/log_viewer.py (1)
webhook_server/libs/log_parser.py (3)
  • parse_json_log_entry (313-363)
  • parse_log_entry (129-183)
  • get_raw_json_entry (424-439)
🪛 Ruff (0.14.10)
webhook_server/utils/structured_logger.py

171-172: Logging statement uses f-string

(G004)


177-178: Logging statement uses f-string

(G004)


257-257: Logging statement uses f-string

(G004)


262-262: Logging statement uses f-string

(G004)


282-282: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/tests/test_structured_logger.py

303-303: Boolean positional value in function call

(FBT003)


320-320: Boolean positional value in function call

(FBT003)


437-437: Boolean positional value in function call

(FBT003)


633-633: Unused method argument: tmp_path

(ARG002)

webhook_server/web/log_viewer.py

570-570: Abstract raise to an inner function

(TRY301)


570-570: Avoid specifying long messages outside the exception class

(TRY003)


854-854: Do not catch blind exception: Exception

(BLE001)


855-855: Logging statement uses f-string

(G004)

🔇 Additional comments (7)
webhook_server/web/log_viewer.py (3)

536-573: LGTM - Clean JSON workflow step retrieval.

The get_workflow_steps_json method provides efficient access to structured workflow data directly from JSON logs. The return structure includes all relevant fields (hook_id, event_type, timing, steps, etc.) and the 404 handling via HTTPException is appropriate.

One minor note: the static analyzer flags TRY301/TRY003 for raising ValueError with a long message at line 570, but this pattern is idiomatic for this codebase since the message is immediately caught and converted to HTTPException at line 573.


588-593: Good fallback strategy for backward compatibility.

The JSON-first approach with graceful fallback to text log parsing ensures newer JSON logs are used when available while maintaining compatibility with existing text-based logs. This is the right pattern for migration.


758-771: Well-structured file prioritization for JSON logs.

The sort key elegantly prioritizes JSON webhook files (webhooks_*.json) over text logs while maintaining newest-first ordering within each category. This ensures the richer structured data is consumed first when available.

webhook_server/utils/structured_logger.py (4)

1-51: Well-documented module with clear architecture.

The module docstring provides excellent documentation of:

  • JSON format (pretty-printed, blank line separators)
  • File naming and rotation strategy
  • Atomic write and concurrency patterns
  • Usage examples

The cross-platform fcntl handling is correctly implemented with a feature flag.


123-179: Solid atomic write pattern with proper locking.

The implementation correctly:

  1. Creates temp file in same directory (ensures atomic rename on same filesystem)
  2. Acquires exclusive lock on temp file during write
  3. Acquires exclusive lock on target file during append
  4. Properly releases locks in finally blocks
  5. Cleans up temp file even on failure
  6. Logs errors without re-raising (correct for non-critical logging operations)

The f-string usage in logging statements follows the established codebase convention. Based on learnings, this is the preferred style.


181-263: Comprehensive error logging for early failures.

The write_error_log method handles two important scenarios:

  1. With partial context: Marks the existing context as failed and preserves any existing error details (line 206 check prevents overwriting)
  2. Without context: Creates a minimal error entry matching the WebhookContext.to_dict() structure for schema consistency

This ensures webhook failures are logged even when processing fails before context is fully populated.


266-290: Clean convenience API for webhook log writing.

The write_webhook_log function provides a simple interface that:

  • Automatically retrieves context from ContextVar when not explicitly provided
  • Raises a clear ValueError if no context is available (helpful for debugging misconfigured callers)
  • Handles Config and StructuredLogWriter instantiation internally

The static analyzer flags TRY003 for the long exception message at line 282, but clear error messages are valuable for debugging. This is acceptable.

@myakove
Copy link
Collaborator Author

myakove commented Jan 5, 2026

CodeRabbit Review Comments - Addressed

All 15 CodeRabbit review comments have been addressed:

Implemented Changes (14)

  1. owners_files_handler.py - Fixed CancelledError handling, now raises RuntimeError on git diff failure instead of returning empty list
  2. pull_request_review_handler.py - Added from __future__ import annotations, added -> None return type on __init__
  3. push_handler.py - Added fail_step() calls with traceback in exception handlers
  4. log_parser.py - Changed logger.error() to logger.exception() for automatic traceback
  5. test_context.py - Added from __future__ import annotations for type annotation fix
  6. test_github_api.py - Fixed redundant info_calls, added OwnersFileHandler.initialize mock
  7. test_log_parser.py - Fixed datetime UTC, async type annotations, non-blocking file I/O with asyncio.to_thread()
  8. test_log_viewer.py - Fixed unused tmp_path lint warning
  9. test_structured_logger.py - Fixed @patch syntax (new=False), unused parameter
  10. structured_logger.py - Prefer context.completed_at as source of truth, fallback to datetime.now(UTC)
  11. log_viewer.py - Memory-bounded streaming with collections.deque(maxlen=remaining)
  12. issue_comment_handler.py - Added fail_step() with traceback on errors
  13. app.py - Added explicit CancelledError handler before generic Exception
  14. pull_request_handler.py - Added CancelledError handler, changed to logger.exception()

Analyzed - No Changes Needed (1)

  1. app_utils.py - CodeRabbit suggested defensive checks for missing duration_ms/status. Analysis determined this violates the project's anti-defensive-programming rules (see CLAUDE.md). The complete_step() and fail_step() methods ALWAYS set these fields - defensive checks are unnecessary overhead.

✅ All 1077 tests pass with 90.45% coverage

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 5, 2026
@myakove
Copy link
Collaborator Author

myakove commented Jan 5, 2026

CodeRabbit Review Response (Review #3626307511)

Addressed (3)

Title File
Memory concern with readlines() webhook_server/web/log_viewer.py
Timing source-of-truth concern webhook_server/utils/structured_logger.py
Test name no longer reflects failure mode webhook_server/tests/test_log_api.py

Notes:

  • log_viewer.py and structured_logger.py were already fixed in previous review cycle
  • test_log_api.py test renamed to test_stream_log_entries_file_read_error

Automated response from CodeRabbit Review Handler

renovate bot and others added 4 commits January 5, 2026 13:28
This PR contains the following updates:

| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |

🔧 This Pull Request updates lock files to use the latest dependency
versions.

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/myk-org/github-webhook-server).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi42OS4xIiwidXBkYXRlZEluVmVyIjoiNDIuNjkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
- Fix CancelledError handling in handlers (re-raise instead of catching)
- Add fail_step() with traceback in exception handlers
- Use logger.exception() instead of logger.error() for automatic traceback
- Add from __future__ import annotations for type annotation fixes
- Fix test mocks for OwnersFileHandler.initialize
- Rename test_stream_log_entries_parse_error to test_stream_log_entries_file_read_error
- Memory-efficient streaming with collections.deque in log_viewer
- Prefer context.completed_at as timing source of truth
- CLAUDE.md: Add section on WebhookContext system and step tracking
- README.md: Add section on JSON webhook logs with format and access methods
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: 6

Fix all issues with AI Agents 🤖
In @webhook_server/app.py:
- Around line 6-10: The finally block in process_with_error_handling currently
calls write_webhook_log(ctx) directly which can raise and prevent
clear_context() from running; wrap the write_webhook_log(ctx) call in its own
try/except that catches Exception, logs the failure with the repository-scoped
logger (use _logger.exception and include _log_context in the message), and then
ensure clear_context() runs in a finally clause; keep log_webhook_summary(ctx,
_logger, _log_context) before the write and do not suppress the original
processing exception.

In @webhook_server/libs/handlers/issue_comment_handler.py:
- Around line 54-55: The issue: process_comment_webhook_data collects exceptions
from asyncio.gather and then logs a traceback using traceback.format_exc()
outside an except block (producing "NoneType: None") and also wraps
asyncio.CancelledError in a RuntimeError so cancellation is suppressed. Fix by
formatting the traceback from the actual exception object (use
traceback.format_exception on the first_exception and pass that string to
ctx.fail_step in process_comment_webhook_data) and by special‑casing
asyncio.CancelledError when iterating over results from asyncio.gather (re‑raise
CancelledError immediately instead of adding it to failed_commands), plus add a
top‑level except asyncio.CancelledError: raise to let cancellation propagate;
update references in the code around process_comment_webhook_data, the results
loop handling the gather call, and the ctx.fail_step call to use the
exception-derived traceback string.

In @webhook_server/tests/test_log_viewer.py:
- Around line 383-406: The test
test_stream_json_log_entries_handles_file_read_errors currently uses a no-op
assertion; update it to actually validate behavior by replacing assert
len(entries) >= 0 with a meaningful check: either assert isinstance(entries,
list) to confirm the generator returned a list without raising, or assert that
the valid entry from sample_json_webhook_data is present (e.g., check
any(e.get("hook_id") == "test-hook-123" for e in entries)) so
controller._stream_json_log_entries(max_files=10, max_entries=100) is verified
to skip the unreadable bad_file and still yield the expected JSON entry.

In @webhook_server/web/log_viewer.py:
- Around line 536-573: get_workflow_steps_json always misses pretty‑printed
multi‑line JSON because _stream_json_log_entries and _stream_log_entries treat
each physical line as a full JSON entry; update the readers to accumulate lines
into multi‑line JSON blocks delimited by blank lines (or EOF) before calling
log_parser.get_raw_json_entry / parse_json_log_entry so each yielded item is a
whole JSON object, and/or make StructuredLogWriter produce JSON‑lines to match
the current line‑based parsing; specifically modify _stream_json_log_entries and
the branch in _stream_log_entries that processes webhooks_*.json to buffer lines
into entries (using a rolling buffer/deque to cap memory to max_entries) and
only call get_raw_json_entry / parse_json_log_entry on the joined block,
ensuring you still respect max_files and max_entries and log warnings on parsing
errors.
♻️ Duplicate comments (1)
webhook_server/tests/test_structured_logger.py (1)

437-451: Use keyword new= in boolean @patch for lint clarity (LOW)

Severity: LOW. To satisfy FBT003 and keep tests consistent with other patches in this file, prefer an explicit keyword for the boolean:

-    @patch("webhook_server.utils.structured_logger.HAS_FCNTL", True)
+    @patch("webhook_server.utils.structured_logger.HAS_FCNTL", new=True)
     @patch("fcntl.flock")
     def test_write_error_log_uses_file_locking(self, mock_flock: Mock, log_writer: StructuredLogWriter) -> None:

This makes the intent explicit and avoids positional-boolean warnings without changing behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b5a631 and fd759ab.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .gitignore
  • CLAUDE.md
  • README.md
  • webhook_server/app.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/log_parser.py
  • webhook_server/tests/test_context.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/utils/context.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/web/log_viewer.py
💤 Files with no reviewable changes (1)
  • .gitignore
🧰 Additional context used
📓 Path-based instructions (6)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to __init__() - required parameters are always provided in this self-contained server application
Defensive checks ARE acceptable in destructors (__del__) because they may be called during failed initialization
Defensive checks ARE acceptable for lazy initialization where attributes explicitly start as None - check is legitimate for uninitialized attributes
Never use defensive checks on known library versions controlled in pyproject.toml - e.g., PyGithub >=2.4.0 methods are guaranteed to exist
Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., user.node_id, user.type, sender always exist in webhook payloads
Never use hasattr() for type discrimination - use isinstance() instead for type checking
Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging
ALL PyGithub method calls MUST be wrapped with asyncio.to_thread() to avoid blocking the FastAPI event loop - including .get_*(), .create_*(), .edit(), .add_to_*(), .remove_from_*()
ALL PyGithub property accesses that may trigger API calls MUST be wrapped with asyncio.to_thread() - properties like .draft, .mergeable, .state, .committer, .permissions, .labels, .assignees are NOT safe to access directly
PyGithub PaginatedList iteration MUST be wrapped in asyncio.to_thread() with list conversion - never iterate directly to avoid blocking the event loop
ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports
ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated
Code coverage of 90% or higher is ...

Files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/utils/context.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/libs/log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/app.py
webhook_server/**/*.{py,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Defensive checks ARE acceptable for optional parameters explicitly typed as Type | None - check is legitimate for parameters allowing None

Files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/utils/context.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/libs/log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/app.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test files MUST use TEST_GITHUB_TOKEN = 'ghp_test1234...' with # pragma: allowlist secret comment to avoid security warnings in test tokens

Files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/tests/test_log_viewer.py
webhook_server/libs/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/**/*.py: Internal methods in webhook_server/libs/ can change freely without backward compatibility requirements - return types, method signatures, and implementations may be modified without deprecation warnings
Eliminate unnecessary defensive checks for return type changes and method signature modifications in internal APIs - fail-fast principle is preferred over compatibility

Files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/libs/log_parser.py
webhook_server/libs/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/libs/handlers/**/*.py: Architecture guarantee: repository_data is ALWAYS set before handlers instantiate in GithubWebhook.process() with fail-fast exceptions - no defensive checks needed
Use await asyncio.gather(*tasks, return_exceptions=True) for concurrent non-blocking PyGithub operations to enable parallel processing of multiple GitHub API calls
Use structured logging with contextual parameters via get_logger_with_params() including repository, hook_id, and component-specific identifiers for webhook correlation
Configuration access via Config(repository='org/repo-name') - repository parameter is required for per-repository overrides via .github-webhook-server.yaml
Handler pattern: implement __init__(self, github_webhook: GithubWebhook, ...) and process_event(event_data: dict) -> None for all GitHub event handlers
Use self.github_webhook.unified_api for all GitHub API operations in handlers - never access GitHub API directly

Files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
webhook_server/web/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

FastAPI endpoint handlers must be async (async def) to maintain non-blocking operation of the event loop

Files:

  • webhook_server/web/log_viewer.py
🧠 Learnings (43)
📓 Common learnings
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification to ensure compatibility with GitHub API changes
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation

Applied to files:

  • README.md
  • webhook_server/tests/test_structured_logger.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/web/log_viewer.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/libs/handlers/push_handler.py
  • CLAUDE.md
  • webhook_server/utils/context.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/app.py
📚 Learning: 2025-08-05T21:15:07.060Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 840
File: webhook_server/docs/pr-flow-api.md:17-26
Timestamp: 2025-08-05T21:15:07.060Z
Learning: The PR Flow API endpoint (/logs/api/pr-flow/{hook_id}) in webhook_server/docs/pr-flow-api.md has no built-in authentication and is designed to be deployed in trusted networks or behind a reverse proxy for security, rather than using API-level authentication mechanisms.

Applied to files:

  • README.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/tests/**/*.py : Test files MUST use `TEST_GITHUB_TOKEN = 'ghp_test1234...'` with `# pragma: allowlist secret` comment to avoid security warnings in test tokens

Applied to files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Code coverage of 90% or higher is MANDATORY - measured via `pytest --cov=webhook_server` and enforced in CI

Applied to files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/tests/test_context.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/tests/test_structured_logger.py
  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/utils/context.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/libs/log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/app.py
📚 Learning: 2024-10-08T09:19:56.185Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Eliminate unnecessary defensive checks for return type changes and method signature modifications in internal APIs - fail-fast principle is preferred over compatibility

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/web/log_viewer.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/tests/test_log_api.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_context.py
  • webhook_server/utils/context.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., `user.node_id`, `user.type`, `sender` always exist in webhook payloads

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/issue_comment_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler pattern: implement `__init__(self, github_webhook: GithubWebhook, ...)` and `process_event(event_data: dict) -> None` for all GitHub event handlers

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
  • CLAUDE.md
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks on known library versions controlled in `pyproject.toml` - e.g., PyGithub >=2.4.0 methods are guaranteed to exist

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub property accesses that may trigger API calls MUST be wrapped with `asyncio.to_thread()` - properties like `.draft`, `.mergeable`, `.state`, `.committer`, `.permissions`, `.labels`, `.assignees` are NOT safe to access directly

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/utils/context.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Architecture guarantee: `repository_data` is ALWAYS set before handlers instantiate in `GithubWebhook.process()` with fail-fast exceptions - no defensive checks needed

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
  • CLAUDE.md
  • webhook_server/libs/handlers/issue_comment_handler.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `await asyncio.gather(*tasks, return_exceptions=True)` for concurrent non-blocking PyGithub operations to enable parallel processing of multiple GitHub API calls

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/*check_run*.py : Repository cloning for check_run webhook events MUST be optimized by skipping clone when action != 'completed' or when check name is 'can-be-merged' with non-success conclusion

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-29T10:42:50.163Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:925-926
Timestamp: 2024-10-29T10:42:50.163Z
Learning: In `webhook_server_container/libs/github_api.py`, the method `self._keep_approved_by_approvers_after_rebase()` must be called after removing labels when synchronizing a pull request. Therefore, it should be placed outside the `ThreadPoolExecutor` to ensure it runs sequentially after label removal.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
📚 Learning: 2024-10-29T08:09:57.157Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 612
File: webhook_server_container/libs/github_api.py:2089-2100
Timestamp: 2024-10-29T08:09:57.157Z
Learning: In `webhook_server_container/libs/github_api.py`, when the function `_keep_approved_by_approvers_after_rebase` is called, existing approval labels have already been cleared after pushing new changes, so there's no need to check for existing approvals within this function.

Applied to files:

  • webhook_server/libs/handlers/pull_request_handler.py
  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely without backward compatibility requirements - return types, method signatures, and implementations may be modified without deprecation warnings

Applied to files:

  • webhook_server/web/log_viewer.py
  • webhook_server/utils/structured_logger.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/utils/context.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `self.github_webhook.unified_api` for all GitHub API operations in handlers - never access GitHub API directly

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.{py,ts,tsx} : Defensive checks ARE acceptable for optional parameters explicitly typed as `Type | None` - check is legitimate for parameters allowing None

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to `__init__()` - required parameters are always provided in this self-contained server application

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Defensive checks ARE acceptable for lazy initialization where attributes explicitly start as `None` - check is legitimate for uninitialized attributes

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the GitHub webhook server codebase, `root_approvers` and `root_reviewers` are properties of the GithubWebhook class, not methods, so they can be used with `.copy()` without parentheses.

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-05-10T22:07:53.544Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 773
File: webhook_server/libs/pull_request_handler.py:553-557
Timestamp: 2025-05-10T22:07:53.544Z
Learning: In the webhook server codebase, `root_approvers` and `root_reviewers` are defined as properties in the `GithubWebhook` class, not methods, so they can be accessed without parentheses and used with methods like `.copy()` directly.

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
📚 Learning: 2024-10-14T14:12:28.924Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1638-1639
Timestamp: 2024-10-14T14:12:28.924Z
Learning: In `webhook_server_container/libs/github_api.py`, it is acceptable for `self.repository.owner.email` to be `None` since internal commands can handle this case.

Applied to files:

  • webhook_server/libs/handlers/pull_request_review_handler.py
  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-10-15T10:37:45.791Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 598
File: webhook_server_container/libs/github_api.py:1860-1874
Timestamp: 2024-10-15T10:37:45.791Z
Learning: In the `process_retest_command` method in `webhook_server_container/libs/github_api.py`, `_target_tests` is defined before use.

Applied to files:

  • webhook_server/tests/test_issue_comment_handler.py
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • webhook_server/tests/test_issue_comment_handler.py
  • webhook_server/libs/handlers/issue_comment_handler.py
📚 Learning: 2025-10-28T13:04:00.466Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/handlers/runner_handler.py:491-571
Timestamp: 2025-10-28T13:04:00.466Z
Learning: In webhook_server/libs/handlers/runner_handler.py, the run_build_container method is designed with the pattern that push=True is always called with set_check=False in production code, so no check-run status needs to be finalized after push operations.

Applied to files:

  • webhook_server/libs/handlers/push_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification to ensure compatibility with GitHub API changes

Applied to files:

  • webhook_server/libs/handlers/push_handler.py
  • webhook_server/tests/test_github_api.py
  • webhook_server/app.py
📚 Learning: 2024-10-14T14:13:21.316Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1632-1637
Timestamp: 2024-10-14T14:13:21.316Z
Learning: In the `ProcessGithubWehook` class in `webhook_server_container/libs/github_api.py`, avoid using environment variables to pass tokens because multiple commands with multiple tokens can run at the same time.

Applied to files:

  • webhook_server/libs/handlers/push_handler.py
📚 Learning: 2024-10-15T13:18:38.590Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 602
File: webhook_server_container/libs/github_api.py:2053-2062
Timestamp: 2024-10-15T13:18:38.590Z
Learning: In the `run_podman_command` method in `webhook_server_container/libs/github_api.py`, the `rc` variable is a boolean that returns `True` when the command succeeds and `False` when it fails.

Applied to files:

  • webhook_server/libs/handlers/push_handler.py
📚 Learning: 2024-11-21T13:34:45.218Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 624
File: webhook_server_container/libs/github_api.py:596-604
Timestamp: 2024-11-21T13:34:45.218Z
Learning: In the codebase, aggregation of reviewers and approvers from all OWNERS files is handled within the `assign_reviewers` method.

Applied to files:

  • webhook_server/libs/handlers/owners_files_handler.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Always re-raise `asyncio.CancelledError` - never suppress it in async Python exception handlers

Applied to files:

  • webhook_server/libs/handlers/owners_files_handler.py
  • CLAUDE.md
  • webhook_server/app.py
📚 Learning: 2025-10-30T00:18:06.176Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/github_api.py:111-118
Timestamp: 2025-10-30T00:18:06.176Z
Learning: In webhook_server/libs/github_api.py, when creating temporary directories or performing operations that need repository names, prefer using self.repository_name (from webhook payload, always available) over dereferencing self.repository.name or self.repository_by_github_app.name, which may be None. This avoids AttributeError and keeps the code simple and reliable.

Applied to files:

  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Configuration access via `Config(repository='org/repo-name')` - repository parameter is required for per-repository overrides via `.github-webhook-server.yaml`

Applied to files:

  • webhook_server/libs/handlers/owners_files_handler.py
  • webhook_server/tests/test_github_api.py
📚 Learning: 2024-11-21T16:10:01.777Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 624
File: webhook_server_container/libs/github_api.py:2067-2101
Timestamp: 2024-11-21T16:10:01.777Z
Learning: In `webhook_server_container/libs/github_api.py`, keep the `max_owners_files` limit as a hardcoded value rather than making it configurable through the config.

Applied to files:

  • webhook_server/tests/test_github_api.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Use `logger.exception()` for logging exceptions (automatic traceback) instead of `logger.error(..., exc_info=True)` for cleaner error logging

Applied to files:

  • CLAUDE.md
  • webhook_server/tests/test_log_api.py
  • webhook_server/libs/log_parser.py
  • webhook_server/app.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Defensive checks ARE acceptable in destructors (`__del__`) because they may be called during failed initialization

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : PyGithub PaginatedList iteration MUST be wrapped in `asyncio.to_thread()` with list conversion - never iterate directly to avoid blocking the event loop

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub method calls MUST be wrapped with `asyncio.to_thread()` to avoid blocking the FastAPI event loop - including `.get_*()`, `.create_*()`, `.edit()`, `.add_to_*()`, `.remove_from_*()`

Applied to files:

  • CLAUDE.md
  • webhook_server/utils/context.py
🧬 Code graph analysis (10)
webhook_server/libs/handlers/pull_request_handler.py (1)
webhook_server/utils/context.py (4)
  • WebhookContext (82-372)
  • start_step (149-166)
  • complete_step (168-222)
  • fail_step (261-295)
webhook_server/tests/test_log_parser.py (1)
webhook_server/libs/log_parser.py (9)
  • parse_json_log_entry (313-363)
  • parse_json_log_file (400-422)
  • get_raw_json_entry (424-439)
  • parse_log_entry (129-183)
  • LogFilter (507-620)
  • LogEntry (16-49)
  • get_unique_values (585-601)
  • get_entry_count_by_field (603-620)
  • monitor_log_directory (473-504)
webhook_server/web/log_viewer.py (1)
webhook_server/libs/log_parser.py (2)
  • parse_json_log_entry (313-363)
  • get_raw_json_entry (424-439)
webhook_server/libs/handlers/pull_request_review_handler.py (2)
webhook_server/utils/context.py (3)
  • WebhookContext (82-372)
  • start_step (149-166)
  • complete_step (168-222)
webhook_server/libs/github_api.py (1)
  • GithubWebhook (78-787)
webhook_server/tests/test_issue_comment_handler.py (1)
webhook_server/libs/handlers/issue_comment_handler.py (1)
  • process_comment_webhook_data (66-136)
webhook_server/libs/handlers/push_handler.py (5)
webhook_server/libs/handlers/check_run_handler.py (1)
  • CheckRunHandler (33-484)
webhook_server/utils/helpers.py (1)
  • run_command (266-417)
webhook_server/utils/notification_utils.py (1)
  • send_slack_message (11-35)
webhook_server/libs/github_api.py (1)
  • GithubWebhook (78-787)
webhook_server/utils/context.py (4)
  • WebhookContext (82-372)
  • start_step (149-166)
  • fail_step (261-295)
  • complete_step (168-222)
webhook_server/tests/test_context.py (1)
webhook_server/utils/context.py (9)
  • WebhookContext (82-372)
  • clear_context (427-433)
  • create_context (375-411)
  • get_context (414-424)
  • start_step (149-166)
  • complete_step (168-222)
  • fail_step (261-295)
  • to_dict (334-372)
  • _build_summary (297-332)
webhook_server/libs/handlers/owners_files_handler.py (1)
webhook_server/utils/helpers.py (1)
  • run_command (266-417)
webhook_server/tests/test_github_api.py (1)
webhook_server/libs/handlers/owners_files_handler.py (1)
  • OwnersFileHandler (23-534)
webhook_server/tests/test_log_api.py (1)
webhook_server/web/log_viewer.py (1)
  • _stream_log_entries (734-811)
🪛 LanguageTool
CLAUDE.md

[uncategorized] ~971-~971: The official name of this software platform is spelled with a capital “H”.
Context: ...Pattern:** Handlers access context via github_webhook.ctx: ```python class PullRequ...

(GITHUB)

🪛 Ruff (0.14.10)
webhook_server/tests/test_structured_logger.py

437-437: Boolean positional value in function call

(FBT003)

webhook_server/libs/handlers/pull_request_handler.py

809-809: Logging statement uses f-string

(G004)


890-890: Logging statement uses f-string

(G004)


898-898: Logging statement uses f-string

(G004)


901-901: Logging statement uses f-string

(G004)

webhook_server/web/log_viewer.py

570-570: Abstract raise to an inner function

(TRY301)


570-570: Avoid specifying long messages outside the exception class

(TRY003)


858-858: Do not catch blind exception: Exception

(BLE001)


859-859: Logging statement uses f-string

(G004)

webhook_server/libs/handlers/pull_request_review_handler.py

43-44: Logging statement uses f-string

(G004)


55-55: Logging statement uses f-string

(G004)

webhook_server/utils/structured_logger.py

171-172: Logging statement uses f-string

(G004)


177-178: Logging statement uses f-string

(G004)


257-257: Logging statement uses f-string

(G004)


262-262: Logging statement uses f-string

(G004)


282-282: Avoid specifying long messages outside the exception class

(TRY003)

webhook_server/libs/handlers/push_handler.py

37-37: Logging statement uses f-string

(G004)


38-38: Logging statement uses f-string

(G004)


40-40: Logging statement uses f-string

(G004)


44-44: Logging statement uses f-string

(G004)


50-50: Logging statement uses f-string

(G004)


55-55: Logging statement uses f-string

(G004)


55-55: Redundant exception object included in logging.exception call

(TRY401)

webhook_server/tests/test_context.py

33-33: datetime.datetime() called without a tzinfo argument

(DTZ001)


145-145: Unused method argument: mock_datetime

(ARG002)


193-193: Unused method argument: mock_datetime

(ARG002)


257-257: Unused method argument: mock_datetime

(ARG002)


331-331: Unused method argument: mock_datetime

(ARG002)


350-350: Unused method argument: mock_datetime

(ARG002)


378-378: Unused method argument: mock_datetime

(ARG002)

webhook_server/libs/handlers/owners_files_handler.py

129-129: Logging statement uses f-string

(G004)


130-130: Abstract raise to an inner function

(TRY301)


135-135: Logging statement uses f-string

(G004)


136-136: Consider moving this statement to an else block

(TRY300)


149-149: Logging statement uses f-string

(G004)

webhook_server/utils/context.py

149-149: Dynamically typed expressions (typing.Any) are disallowed in **data

(ANN401)


172-172: Dynamically typed expressions (typing.Any) are disallowed in **data

(ANN401)


261-261: Dynamically typed expressions (typing.Any) are disallowed in **data

(ANN401)

webhook_server/libs/handlers/issue_comment_handler.py

74-74: Logging statement uses f-string

(G004)


79-79: Logging statement uses f-string

(G004)


85-85: Logging statement uses f-string

(G004)


117-117: Logging statement uses f-string

(G004)


127-127: Abstract raise to an inner function

(TRY301)

webhook_server/libs/log_parser.py

418-418: Logging statement uses f-string

(G004)


418-418: Redundant exception object included in logging.exception call

(TRY401)


420-420: Logging statement uses f-string

(G004)


420-420: Redundant exception object included in logging.exception call

(TRY401)

webhook_server/app.py

430-430: Logging statement uses f-string

(G004)


439-439: Logging statement uses f-string

(G004)


443-443: Logging statement uses f-string

(G004)

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 5, 2026
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: 4

Fix all issues with AI Agents 🤖
In @webhook_server/tests/test_edge_cases_validation.py:
- Around line 890-902: The helper function signatures use bare dict which fails
mypy strict checks; update user_request(controller: LogViewerController,
filters: dict) -> dict to use explicit types (e.g., filters: dict[str, Any] and
return type -> dict[str, Any]) and ensure you import Any from typing (or from
collections.abc if using Python 3.10+ style). Also update any related local
helper async_iter_wrapper annotations if needed (e.g., parameter:
list[LogEntry]) so all function parameters and return types are fully annotated.

In @webhook_server/tests/test_log_viewer.py:
- Around line 1098-1104: Add explicit return type annotations to the async test
helper functions so their signatures reflect they return HTML strings; update
the mock helper(s) named mock_get_html used as the side effect for
controller._get_log_viewer_html to declare async def mock_get_html() -> str:
(apply the same change to the second occurrence around controller.get_log_page
tests), keeping the body unchanged so the function still returns the HTML
string.
- Around line 1042-1046: Add full type hints to the async mock close functions:
annotate their parameters (e.g., code: int, reason: str) and the return type as
-> None so they satisfy mypy strict mode; update the async def mock_close(...)
used to set ws1.close and ws2.close and the other analogous mock close function
later in the file to include these parameter and return type annotations.

In @webhook_server/utils/app_utils.py:
- Around line 192-193: The summary step building code calls
format_duration(step_duration_ms) without ensuring step_duration_ms is not None;
update the validation around step_data to check both presence and non-None
(e.g., if "duration_ms" in step_data and step_data["duration_ms"] is not None)
before calling format_duration, or alternatively change format_duration(ms) to
accept None (int | None) and return a placeholder like "unknown" when ms is
None; update references in the steps_summary construction (variable
step_duration_ms and call to format_duration) and tests accordingly.

Caution

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

⚠️ Outside diff range comments (2)
webhook_server/tests/test_log_parser.py (2)

271-278: HIGH: Add complete type hints to _collect_entries helper method

This method violates the mandatory coding guideline requiring complete type hints for ALL functions. Parameters async_gen, entries_list, and max_entries lack type annotations, and the return type is missing.

As per coding guidelines: "ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated."

The correct pattern is already demonstrated at line 1336 in the collect_entries local function within the same file.

🔎 Add type hints matching the pattern at line 1336
-    async def _collect_entries(self, async_gen, entries_list, max_entries=10):
+    async def _collect_entries(
+        self,
+        async_gen: AsyncIterator[LogEntry],
+        entries_list: list[LogEntry],
+        max_entries: int = 10,
+    ) -> None:
         """Helper to collect entries from async generator with a limit."""

Based on coding guidelines, which mandate mypy-strict-compatible type hints for all functions.


252-256: HIGH: Replace blocking file I/O with async-compatible wrapper

This code uses blocking open() and file operations directly in an async function, which violates the mandatory coding guideline requiring all file operations be wrapped with asyncio.to_thread().

As per coding guidelines: "ALL PyGithub method calls MUST be wrapped with asyncio.to_thread() to avoid blocking the FastAPI event loop" - this extends to all blocking I/O operations including file writes.

The correct pattern is demonstrated at lines 1353-1358 in the same file where identical file operations are properly wrapped.

🔎 Wrap file operations using the pattern from lines 1353-1358
         # Give the tail a moment to start
         await asyncio.sleep(0.1)
 
-        # Add new content to the file
-        with open(f.name, "a") as append_f:
-            append_f.write("\n2025-07-31T10:01:00.000000 main DEBUG New entry 1")
-            append_f.write("\n2025-07-31T10:02:00.000000 main ERROR New entry 2")
-            append_f.flush()
+        # Add new content to the file - non-blocking
+        def _append_log() -> None:
+            with open(f.name, "a") as append_f:
+                append_f.write("\n2025-07-31T10:01:00.000000 main DEBUG New entry 1")
+                append_f.write("\n2025-07-31T10:02:00.000000 main ERROR New entry 2")
+                append_f.flush()
+
+        await asyncio.to_thread(_append_log)
 
         # Wait for the tail to collect entries with timeout

Based on coding guidelines, which prohibit blocking I/O in async functions.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa5e4b1 and bf4fd26.

📒 Files selected for processing (5)
  • webhook_server/tests/test_edge_cases_validation.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/utils/app_utils.py
  • webhook_server/web/log_viewer.py
🧰 Additional context used
📓 Path-based instructions (4)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to __init__() - required parameters are always provided in this self-contained server application
Defensive checks ARE acceptable in destructors (__del__) because they may be called during failed initialization
Defensive checks ARE acceptable for lazy initialization where attributes explicitly start as None - check is legitimate for uninitialized attributes
Never use defensive checks on known library versions controlled in pyproject.toml - e.g., PyGithub >=2.4.0 methods are guaranteed to exist
Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., user.node_id, user.type, sender always exist in webhook payloads
Never use hasattr() for type discrimination - use isinstance() instead for type checking
Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging
ALL PyGithub method calls MUST be wrapped with asyncio.to_thread() to avoid blocking the FastAPI event loop - including .get_*(), .create_*(), .edit(), .add_to_*(), .remove_from_*()
ALL PyGithub property accesses that may trigger API calls MUST be wrapped with asyncio.to_thread() - properties like .draft, .mergeable, .state, .committer, .permissions, .labels, .assignees are NOT safe to access directly
PyGithub PaginatedList iteration MUST be wrapped in asyncio.to_thread() with list conversion - never iterate directly to avoid blocking the event loop
ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports
ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated
Code coverage of 90% or higher is ...

Files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
webhook_server/**/*.{py,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Defensive checks ARE acceptable for optional parameters explicitly typed as Type | None - check is legitimate for parameters allowing None

Files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test files MUST use TEST_GITHUB_TOKEN = 'ghp_test1234...' with # pragma: allowlist secret comment to avoid security warnings in test tokens

Files:

  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
webhook_server/web/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

FastAPI endpoint handlers must be async (async def) to maintain non-blocking operation of the event loop

Files:

  • webhook_server/web/log_viewer.py
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely without backward compatibility requirements - return types, method signatures, and implementations may be modified without deprecation warnings

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation

Applied to files:

  • webhook_server/utils/app_utils.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging

Applied to files:

  • webhook_server/utils/app_utils.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/tests/**/*.py : Test files MUST use `TEST_GITHUB_TOKEN = 'ghp_test1234...'` with `# pragma: allowlist secret` comment to avoid security warnings in test tokens

Applied to files:

  • webhook_server/tests/test_log_parser.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/web/**/*.py : FastAPI endpoint handlers must be async (`async def`) to maintain non-blocking operation of the event loop

Applied to files:

  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Eliminate unnecessary defensive checks for return type changes and method signature modifications in internal APIs - fail-fast principle is preferred over compatibility

Applied to files:

  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : PyGithub PaginatedList iteration MUST be wrapped in `asyncio.to_thread()` with list conversion - never iterate directly to avoid blocking the event loop

Applied to files:

  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub method calls MUST be wrapped with `asyncio.to_thread()` to avoid blocking the FastAPI event loop - including `.get_*()`, `.create_*()`, `.edit()`, `.add_to_*()`, `.remove_from_*()`

Applied to files:

  • webhook_server/web/log_viewer.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub property accesses that may trigger API calls MUST be wrapped with `asyncio.to_thread()` - properties like `.draft`, `.mergeable`, `.state`, `.committer`, `.permissions`, `.labels`, `.assignees` are NOT safe to access directly

Applied to files:

  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `await asyncio.gather(*tasks, return_exceptions=True)` for concurrent non-blocking PyGithub operations to enable parallel processing of multiple GitHub API calls

Applied to files:

  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Use `logger.exception()` for logging exceptions (automatic traceback) instead of `logger.error(..., exc_info=True)` for cleaner error logging

Applied to files:

  • webhook_server/web/log_viewer.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Always re-raise `asyncio.CancelledError` - never suppress it in async Python exception handlers

Applied to files:

  • webhook_server/web/log_viewer.py
📚 Learning: 2025-08-05T21:15:07.060Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 840
File: webhook_server/docs/pr-flow-api.md:17-26
Timestamp: 2025-08-05T21:15:07.060Z
Learning: The PR Flow API endpoint (/logs/api/pr-flow/{hook_id}) in webhook_server/docs/pr-flow-api.md has no built-in authentication and is designed to be deployed in trusted networks or behind a reverse proxy for security, rather than using API-level authentication mechanisms.

Applied to files:

  • webhook_server/web/log_viewer.py
🧬 Code graph analysis (4)
webhook_server/utils/app_utils.py (3)
webhook_server/utils/context.py (1)
  • WebhookContext (82-372)
webhook_server/utils/helpers.py (1)
  • get_logger_with_params (31-92)
webhook_server/web/log_viewer.py (1)
  • LogViewerController (22-1130)
webhook_server/tests/test_log_viewer.py (1)
webhook_server/web/log_viewer.py (8)
  • _stream_json_log_entries (835-890)
  • get_workflow_steps_json (547-584)
  • get_workflow_steps (586-697)
  • _stream_log_entries (748-833)
  • get_log_entries (91-224)
  • _estimate_total_log_count (1083-1130)
  • _build_log_prefix_from_context (516-545)
  • _generate_json_export (989-1008)
webhook_server/web/log_viewer.py (1)
webhook_server/libs/log_parser.py (3)
  • LogEntry (16-49)
  • parse_json_log_entry (313-374)
  • parse_log_entry (129-183)
webhook_server/tests/test_edge_cases_validation.py (1)
webhook_server/web/log_viewer.py (3)
  • LogViewerController (22-1130)
  • get_log_entries (91-224)
  • export_logs (271-385)
🪛 Ruff (0.14.10)
webhook_server/utils/app_utils.py

176-176: Avoid specifying long messages outside the exception class

(TRY003)


183-185: Avoid specifying long messages outside the exception class

(TRY003)


187-190: Avoid specifying long messages outside the exception class

(TRY003)


203-204: Logging statement uses f-string

(G004)

webhook_server/tests/test_log_viewer.py

200-200: Async functions should not open files with blocking methods like open

(ASYNC230)


375-375: Async functions should not open files with blocking methods like open

(ASYNC230)


507-507: Async functions should not open files with blocking methods like open

(ASYNC230)


528-528: Async functions should not open files with blocking methods like open

(ASYNC230)


554-554: Async functions should not open files with blocking methods like open

(ASYNC230)


590-590: Async functions should not open files with blocking methods like open

(ASYNC230)


634-634: Async functions should not open files with blocking methods like open

(ASYNC230)


658-658: Async functions should not open files with blocking methods like open

(ASYNC230)


678-678: Async functions should not open files with blocking methods like open

(ASYNC230)


703-703: Async functions should not open files with blocking methods like open

(ASYNC230)


759-759: Async functions should not open files with blocking methods like open

(ASYNC230)


798-798: Async functions should not open files with blocking methods like open

(ASYNC230)


843-843: Async functions should not open files with blocking methods like open

(ASYNC230)


864-864: Async functions should not open files with blocking methods like open

(ASYNC230)


910-910: Async functions should not open files with blocking methods like open

(ASYNC230)


933-933: Unused function argument: args

(ARG001)


933-933: Unused function argument: kwargs

(ARG001)


934-934: Avoid specifying long messages outside the exception class

(TRY003)


969-969: Async functions should not open files with blocking methods like open

(ASYNC230)


1003-1003: Async functions should not open files with blocking methods like open

(ASYNC230)


1042-1042: Missing return type annotation for private function mock_close

Add return type annotation: None

(ANN202)


1063-1063: Missing return type annotation for private function mock_close_error

Add return type annotation: Never

(ANN202)


1063-1063: Unused function argument: code

(ARG001)


1063-1063: Unused function argument: reason

(ARG001)


1064-1064: Create your own exception

(TRY002)


1064-1064: Avoid specifying long messages outside the exception class

(TRY003)


1098-1098: Missing return type annotation for private function mock_get_html

Add return type annotation: str

(ANN202)


1110-1110: Missing return type annotation for private function mock_get_html

(ANN202)

webhook_server/web/log_viewer.py

581-581: Abstract raise to an inner function

(TRY301)


581-581: Avoid specifying long messages outside the exception class

(TRY003)


832-832: Do not catch blind exception: Exception

(BLE001)


833-833: Logging statement uses f-string

(G004)


889-889: Do not catch blind exception: Exception

(BLE001)


890-890: Logging statement uses f-string

(G004)


1112-1112: Logging statement uses f-string

(G004)

🔇 Additional comments (16)
webhook_server/utils/app_utils.py (3)

126-156: LGTM: Clean duration formatting with proper edge case handling.

The implementation correctly handles the progression from milliseconds through hours. The logic is clear and the format strings are user-friendly.

One minor observation: if ms is negative (which shouldn't happen in normal operation), the function will return something like -500ms. Consider whether negative durations are meaningful or indicate a bug upstream.


159-205: Explicit validation aligns with fail-fast principles.

The explicit field validation on lines 182-190 correctly implements the coding guideline to raise exceptions rather than masking missing data with defaults. This was addressed from a previous review comment.

The f-string logging on lines 203-204 is acceptable per project conventions (f-strings are the established style for logging interpolation).

Regarding static analysis TRY003 hints about long exception messages outside classes: these are informational messages that help developers debug instrumentation gaps. The verbosity is justified here since the context ("ensure complete_step() or fail_step() was called") is actionable.


208-221: Simple passthrough wrapper is appropriate.

The get_workflow_steps_core function provides a clean abstraction layer between the utility module and the controller. Type hints are complete and the async signature is correct.

webhook_server/tests/test_log_viewer.py (5)

111-126: Blocking open() in test fixtures is acceptable.

Severity: LOW (false positive from static analysis).

Ruff flags ASYNC230 for using blocking open() inside async test methods. However, this is test fixture setup code that creates synthetic log files before exercising async controller methods. The blocking I/O doesn't affect test correctness since:

  1. File creation is a one-time setup operation
  2. Tests run sequentially, not concurrently
  3. The actual controller methods under test use aiofiles properly

No change needed.


84-109: Fixture timing schema now matches production.

The sample_json_webhook_data fixture correctly uses completed_at and duration_ms: 5000 to match the production schema from WebhookContext.to_dict() and StructuredLogWriter. This was addressed from a previous review comment.


933-935: Unused mock arguments are intentional for interface matching.

Severity: LOW (false positive).

The *args and **kwargs in mock_stream_error are required to match the signature of _stream_log_entries. Ruff's ARG001 warning is a false positive here since mock functions often need to accept arguments they don't use.

The yield after raise (line 935) is a clever pattern to make Python recognize this as an async generator while ensuring it always raises before yielding.


128-148: Good use of shallow copy for independent test entries.

The test correctly uses .copy() for simple dictionary mutations. Note that for nested structures like entry2["pr"]["number"] = 789 on line 138, this modifies the shared nested dict. This works here because sample_json_webhook_data is a fixture that returns a fresh dict each call.


471-499: Correct use of copy.deepcopy for nested structure independence.

Unlike the earlier tests that used shallow .copy(), this test correctly uses copy.deepcopy() to ensure modifications to nested pr dictionaries don't affect other entries. Good attention to detail.

webhook_server/web/log_viewer.py (8)

547-584: JSON-first workflow retrieval with proper fallback.

The get_workflow_steps_json method efficiently searches JSONL logs and returns structured data. The fallback to text parsing in get_workflow_steps (lines 599-604) maintains backward compatibility.

One note: The steps field defaults to {} (line 575) which is correct since workflow_steps is a dictionary mapping step names to their data, not a list. This was clarified in a previous review exchange.


806-819: Async file I/O with proper JSONL parsing.

The switch to aiofiles.open() correctly makes file reading non-blocking. The parser branch based on file suffix (.json vs text logs) is clean.

The JSONL assumption (one JSON object per line) now matches the StructuredLogWriter format change that writes compact JSON. This resolves the previous critical issue about multi-line JSON incompatibility.


835-890: Memory-bounded JSON streaming with proper cancellation handling.

The _stream_json_log_entries method correctly:

  1. Uses deque(maxlen=remaining) to bound memory (addresses previous review concern)
  2. Re-raises asyncio.CancelledError per coding guidelines
  3. Processes JSONL format line-by-line
  4. Yields entries in reverse order (newest first)

The broad Exception catch on line 889 is acceptable here since file streaming should be resilient to individual file errors while continuing to process other files.


213-224: Consistent asyncio.CancelledError handling throughout.

All async methods now properly catch and re-raise CancelledError before the general Exception handler. This follows the coding guideline: "Always re-raise asyncio.CancelledError - never suppress it in async Python exception handlers."

The pattern is correct:

except asyncio.CancelledError:
    self.logger.debug("Operation cancelled")
    raise  # Always re-raise CancelledError

Also applies to: 373-385, 502-514, 685-697, 829-833


916-936: Async HTML template loading with graceful fallback.

The _get_log_viewer_html method correctly uses aiofiles for non-blocking template loading. The fallback HTML on FileNotFoundError or OSError ensures the log viewer degrades gracefully rather than crashing.


1102-1113: Narrowed exception handling in log estimation.

The exception handling was narrowed from broad Exception to (OSError, PermissionError) per a previous review suggestion. The debug-level logging is appropriate for this non-critical estimation code.


778-788: JSON webhook files prioritized in sort order.

The sort key function correctly prioritizes webhooks_*.json files (primary data source) over text logs. The tuple (0 if is_json_webhook else 1, -mtime) ensures JSON files appear first, then sorted by modification time within each category.


581-581: Ruff TRY003/TRY301 hints are acceptable here.

Severity: LOW (style preference).

Ruff suggests abstracting the raise ValueError(...) to an inner function and avoiding long messages. However:

  1. The message is concise and actionable
  2. This is an internal method, not a public API
  3. Creating a custom exception class for this single use case would be over-engineering

No change needed.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 5, 2026
- Add explicit dict[str, Any] type hints in test_edge_cases_validation.py
- Add return type annotations to mock functions in test_log_viewer.py
- Add None check for duration_ms to prevent type errors in app_utils.py
- Add type hints to _collect_entries helper in test_log_parser.py
- Replace blocking file I/O with asyncio.to_thread in test_log_parser.py
Reduce file size from 40.2k to 18.3k chars by:
- Merging duplicate PyGithub asyncio.to_thread sections
- Reducing before/after examples
- Removing redundant decision tree
- Condensing verbose explanations
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 @CLAUDE.md:
- Line 37: Fix MD022/MD031 violations in CLAUDE.md by adding blank lines above
all headings (except the very first line of the file) and adding a blank line
both above and below every code block fence. Locate each heading and fenced code
block (triple backticks) in the document (e.g., the Python block starting with
```python and all other ``` fences) and insert the required blank lines so
headings are preceded by an empty line and code fences have an empty line before
and after; run markdownlint or the pre-commit hooks to validate no MD022/MD031
errors remain.

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_edge_cases_validation.py (1)

857-920: Excellent concurrent user test with precise type hints

All previously flagged type hint issues have been addressed:

  • user_filters explicitly typed as list[dict[str, Any]] (line 883)
  • user_request has complete parameter and return type annotations (line 891)
  • async_iter_wrapper includes AsyncIterator[LogEntry] return type (line 894)
  • All async operations properly awaited

The test effectively validates concurrent multi-user scenarios with different filter combinations.

Optional observation: async_iter_wrapper is defined in multiple test methods (lines 767-772, 894-899). This duplication provides test isolation and clarity, which is acceptable. If you later want to reduce duplication, consider extracting to a module-level test helper.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf4fd26 and 9a00931.

📒 Files selected for processing (5)
  • CLAUDE.md
  • webhook_server/tests/test_edge_cases_validation.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/utils/app_utils.py
🧰 Additional context used
📓 Path-based instructions (3)
webhook_server/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

webhook_server/**/*.py: Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to __init__() - required parameters are always provided in this self-contained server application
Defensive checks ARE acceptable in destructors (__del__) because they may be called during failed initialization
Defensive checks ARE acceptable for lazy initialization where attributes explicitly start as None - check is legitimate for uninitialized attributes
Never use defensive checks on known library versions controlled in pyproject.toml - e.g., PyGithub >=2.4.0 methods are guaranteed to exist
Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., user.node_id, user.type, sender always exist in webhook payloads
Never use hasattr() for type discrimination - use isinstance() instead for type checking
Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging
ALL PyGithub method calls MUST be wrapped with asyncio.to_thread() to avoid blocking the FastAPI event loop - including .get_*(), .create_*(), .edit(), .add_to_*(), .remove_from_*()
ALL PyGithub property accesses that may trigger API calls MUST be wrapped with asyncio.to_thread() - properties like .draft, .mergeable, .state, .committer, .permissions, .labels, .assignees are NOT safe to access directly
PyGithub PaginatedList iteration MUST be wrapped in asyncio.to_thread() with list conversion - never iterate directly to avoid blocking the event loop
ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports
ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated
Code coverage of 90% or higher is ...

Files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
webhook_server/**/*.{py,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Defensive checks ARE acceptable for optional parameters explicitly typed as Type | None - check is legitimate for parameters allowing None

Files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
webhook_server/tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test files MUST use TEST_GITHUB_TOKEN = 'ghp_test1234...' with # pragma: allowlist secret comment to avoid security warnings in test tokens

Files:

  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
🧠 Learnings (34)
📓 Common learnings
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Internal methods in `webhook_server/libs/` can change freely without backward compatibility requirements - return types, method signatures, and implementations may be modified without deprecation warnings

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use structured logging with contextual parameters via `get_logger_with_params()` including repository, hook_id, and component-specific identifiers for webhook correlation

Applied to files:

  • webhook_server/utils/app_utils.py
  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging

Applied to files:

  • webhook_server/utils/app_utils.py
  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.{py,ts,tsx} : Defensive checks ARE acceptable for optional parameters explicitly typed as `Type | None` - check is legitimate for parameters allowing None

Applied to files:

  • webhook_server/utils/app_utils.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL functions must have complete type hints compatible with mypy strict mode - parameter types, return types, and complex object types must be explicitly annotated

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • CLAUDE.md
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-12-29T13:09:56.231Z
Learnt from: rnetser
Repo: myk-org/github-webhook-server PR: 959
File: webhook_server/libs/handlers/pull_request_handler.py:887-887
Timestamp: 2025-12-29T13:09:56.231Z
Learning: In the myk-org/github-webhook-server repository, logging statements in the webhook_server codebase should use f-strings for interpolation. Do not suggest changing to lazy formatting with % (e.g., logger.debug('msg %s', var)) since that is an established style choice. When adding new log statements, prefer f"...{var}..." for readability and performance, and avoid string concatenation or format-style logging unless the project explicitly adopts a different convention.

Applied to files:

  • webhook_server/utils/app_utils.py
  • webhook_server/tests/test_log_viewer.py
  • webhook_server/tests/test_log_parser.py
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Architecture guarantee: `repository_data` is ALWAYS set before handlers instantiate in `GithubWebhook.process()` with fail-fast exceptions - no defensive checks needed

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler pattern: implement `__init__(self, github_webhook: GithubWebhook, ...)` and `process_event(event_data: dict) -> None` for all GitHub event handlers

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks (hasattr, if object:, if value is not None) on required parameters passed to `__init__()` - required parameters are always provided in this self-contained server application

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks on webhook payload fields guaranteed by GitHub webhook specification - e.g., `user.node_id`, `user.type`, `sender` always exist in webhook payloads

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Never use defensive checks on known library versions controlled in `pyproject.toml` - e.g., PyGithub >=2.4.0 methods are guaranteed to exist

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `self.github_webhook.unified_api` for all GitHub API operations in handlers - never access GitHub API directly

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub method calls MUST be wrapped with `asyncio.to_thread()` to avoid blocking the FastAPI event loop - including `.get_*()`, `.create_*()`, `.edit()`, `.add_to_*()`, `.remove_from_*()`

Applied to files:

  • CLAUDE.md
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Defensive checks ARE acceptable in destructors (`__del__`) because they may be called during failed initialization

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL PyGithub property accesses that may trigger API calls MUST be wrapped with `asyncio.to_thread()` - properties like `.draft`, `.mergeable`, `.state`, `.committer`, `.permissions`, `.labels`, `.assignees` are NOT safe to access directly

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/**/*.py : Eliminate unnecessary defensive checks for return type changes and method signature modifications in internal APIs - fail-fast principle is preferred over compatibility

Applied to files:

  • CLAUDE.md
  • webhook_server/tests/test_edge_cases_validation.py
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/*check_run*.py : Repository cloning for check_run webhook events MUST be optimized by skipping clone when action != 'completed' or when check name is 'can-be-merged' with non-success conclusion

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Configuration access via `Config(repository='org/repo-name')` - repository parameter is required for per-repository overrides via `.github-webhook-server.yaml`

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : PyGithub PaginatedList iteration MUST be wrapped in `asyncio.to_thread()` with list conversion - never iterate directly to avoid blocking the event loop

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-30T00:18:06.176Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 878
File: webhook_server/libs/github_api.py:111-118
Timestamp: 2025-10-30T00:18:06.176Z
Learning: In webhook_server/libs/github_api.py, when creating temporary directories or performing operations that need repository names, prefer using self.repository_name (from webhook payload, always available) over dereferencing self.repository.name or self.repository_by_github_app.name, which may be None. This avoids AttributeError and keeps the code simple and reliable.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-05-13T12:06:27.297Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 778
File: webhook_server/libs/pull_request_handler.py:327-330
Timestamp: 2025-05-13T12:06:27.297Z
Learning: In the GitHub webhook server, synchronous GitHub API calls (like create_issue_comment, add_to_assignees, etc.) in async methods should be awaited using asyncio.to_thread or loop.run_in_executor to prevent blocking the event loop.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Use `logger.exception()` for logging exceptions (automatic traceback) instead of `logger.error(..., exc_info=True)` for cleaner error logging

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : Code coverage of 90% or higher is MANDATORY - measured via `pytest --cov=webhook_server` and enforced in CI

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/**/*.py : ALL imports must be at the top of files - no imports in the middle of functions or try/except blocks except for TYPE_CHECKING conditional imports

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/tests/**/*.py : Test files MUST use `TEST_GITHUB_TOKEN = 'ghp_test1234...'` with `# pragma: allowlist secret` comment to avoid security warnings in test tokens

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Run code quality checks via `uv run ruff format && uv run ruff check --fix && uv run mypy webhook_server/` before committing - enforced by pre-commit hooks

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Log viewer endpoints (`/logs/*`) are unauthenticated by design - deploy only on trusted networks and never expose to public internet as logs contain sensitive data

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Always mask sensitive data in logs using `mask-sensitive-data: true` configuration - apply masking to tokens, webhook payloads, and user information

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-08-05T21:15:07.060Z
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 840
File: webhook_server/docs/pr-flow-api.md:17-26
Timestamp: 2025-08-05T21:15:07.060Z
Learning: The PR Flow API endpoint (/logs/api/pr-flow/{hook_id}) in webhook_server/docs/pr-flow-api.md has no built-in authentication and is designed to be deployed in trusted networks or behind a reverse proxy for security, rather than using API-level authentication mechanisms.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `await asyncio.gather(*tasks, return_exceptions=True)` for concurrent non-blocking PyGithub operations to enable parallel processing of multiple GitHub API calls

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/config/schema.yaml : Configuration schema must be defined in `webhook_server/config/schema.yaml` with validation logic in `webhook_server/libs/config.py`

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification to ensure compatibility with GitHub API changes

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T12:39:43.079Z
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/web/**/*.py : FastAPI endpoint handlers must be async (`async def`) to maintain non-blocking operation of the event loop

Applied to files:

  • webhook_server/tests/test_edge_cases_validation.py
🧬 Code graph analysis (3)
webhook_server/utils/app_utils.py (3)
webhook_server/utils/context.py (1)
  • WebhookContext (82-372)
webhook_server/utils/helpers.py (1)
  • get_logger_with_params (31-92)
webhook_server/web/log_viewer.py (2)
  • LogViewerController (22-1130)
  • get_workflow_steps (586-697)
webhook_server/tests/test_log_parser.py (1)
webhook_server/libs/log_parser.py (4)
  • LogEntry (16-49)
  • parse_log_entry (129-183)
  • extract_token_spend (261-280)
  • monitor_log_directory (504-535)
webhook_server/tests/test_edge_cases_validation.py (2)
webhook_server/libs/log_parser.py (1)
  • LogEntry (16-49)
webhook_server/web/log_viewer.py (2)
  • get_log_entries (91-224)
  • export_logs (271-385)
🪛 LanguageTool
CLAUDE.md

[uncategorized] ~190-~190: The official name of this software platform is spelled with a capital “H”.
Context: ... main FastAPI app (app.py) - Pattern: __init__(github_webhook, ...) → `process_event(event_d...

(GITHUB)


[typographical] ~329-~329: Consider using an en dash here instead of a hyphen.
Context: ... is synchronous - each operation blocks 100ms-2 seconds - Blocking = frozen server (no ...

(QB_NEW_EN_DASH_RULE_EN)


[uncategorized] ~372-~372: The official name of this software platform is spelled with a capital “H”.
Context: ...ion for check_run Events Location: webhook_server/libs/github_api.py lines 534-570 **Early exit con...

(GITHUB)

🪛 markdownlint-cli2 (0.18.1)
CLAUDE.md

37-37: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


43-43: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


48-48: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


53-53: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


64-64: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


65-65: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


75-75: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


77-77: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


85-85: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


87-87: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


95-95: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


97-97: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


105-105: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


106-106: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


119-119: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


129-129: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


159-159: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


173-173: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


211-211: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


212-212: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


217-217: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


218-218: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


226-226: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


227-227: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


433-433: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


447-447: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


462-462: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


480-480: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


523-523: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


525-525: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


533-533: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🪛 Ruff (0.14.10)
webhook_server/utils/app_utils.py

176-176: Avoid specifying long messages outside the exception class

(TRY003)


183-185: Avoid specifying long messages outside the exception class

(TRY003)


187-190: Avoid specifying long messages outside the exception class

(TRY003)


203-204: Logging statement uses f-string

(G004)

webhook_server/tests/test_log_viewer.py

200-200: Async functions should not open files with blocking methods like open

(ASYNC230)


375-375: Async functions should not open files with blocking methods like open

(ASYNC230)


507-507: Async functions should not open files with blocking methods like open

(ASYNC230)


528-528: Async functions should not open files with blocking methods like open

(ASYNC230)


554-554: Async functions should not open files with blocking methods like open

(ASYNC230)


590-590: Async functions should not open files with blocking methods like open

(ASYNC230)


634-634: Async functions should not open files with blocking methods like open

(ASYNC230)


658-658: Async functions should not open files with blocking methods like open

(ASYNC230)


678-678: Async functions should not open files with blocking methods like open

(ASYNC230)


703-703: Async functions should not open files with blocking methods like open

(ASYNC230)


759-759: Async functions should not open files with blocking methods like open

(ASYNC230)


798-798: Async functions should not open files with blocking methods like open

(ASYNC230)


843-843: Async functions should not open files with blocking methods like open

(ASYNC230)


864-864: Async functions should not open files with blocking methods like open

(ASYNC230)


910-910: Async functions should not open files with blocking methods like open

(ASYNC230)


933-933: Unused function argument: args

(ARG001)


933-933: Unused function argument: kwargs

(ARG001)


934-934: Avoid specifying long messages outside the exception class

(TRY003)


969-969: Async functions should not open files with blocking methods like open

(ASYNC230)


1003-1003: Async functions should not open files with blocking methods like open

(ASYNC230)


1063-1063: Unused function argument: code

(ARG001)


1063-1063: Unused function argument: reason

(ARG001)


1064-1064: Create your own exception

(TRY002)


1064-1064: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (10)
CLAUDE.md (4)

428-492: Verify structured logging section details against actual implementation.

The new structured logging section (lines 428-492) introduces context creation, step tracking, and log format documentation. Ensure the following match the actual implementation:

  1. Context creation parameters (line 436-443): hook_id, event_type, repository, action, sender, api_user — verify these match WebhookContext dataclass signature
  2. Step tracking methods (line 451-459): start_step(), complete_step(), fail_step() with exception and traceback_str parameters — verify method signatures
  3. Context access pattern (line 450): get_context() — verify this correctly retrieves the ContextVar-backed context
  4. Log file path format (line 475): {config.data_dir}/logs/webhooks_YYYY-MM-DD.json — verify this matches StructuredLogger implementation
  5. JSON structure (lines 480-492): Field names and nesting (pr, timing, workflow_steps, token_spend, success) — verify against actual output

This documentation sets expectations for developers, so accuracy is critical. If any parameter names, method signatures, or file paths differ from the implementation, update the documentation to match.


265-327: Verify PyGithub property access list (line 275) for completeness and accuracy.

The non-blocking guidance section lists properties that trigger API calls: .draft, .mergeable, .state, .committer, .author, .permissions, .labels, .assignees. Ensure this list is:

  1. Complete — Are there other commonly-used properties that also block and should be listed?
  2. Accurate — Do all listed properties actually trigger API calls, or are some cached from webhook payload?
  3. Consistent — Does the guidance match what's verified in test cases and actual handler code?

This list drives developer behavior, so accuracy is critical.


1-600: Document reflects PR migration well; address formatting and verify technical details.

Strengths:

  • Comprehensive coverage of structured JSON logging patterns with clear examples
  • New non-blocking PyGithub guidance with decision tree is well-explained and critical for async correctness
  • Anti-defensive programming section expanded with clear violations and acceptable patterns
  • Architecture guarantees section provides helpful context for when defensive checks are unnecessary
  • Alignment with PR objectives: ContextVar-based context, structured logging, fail-fast principle

Action items (before merge):

  1. Fix 20+ markdown formatting violations (blank lines around headings and code blocks)
  2. Correct "GitHub" capitalization (should be capitalized, not "github")
  3. Verify technical accuracy of structured logging section (context parameters, method signatures, file paths) — see companion comment above
  4. Verify PyGithub property access list is complete and accurate — see companion comment above

These are fixable issues that don't indicate deeper problems with the content.


190-190: Capitalize GitHub (proper noun) consistently.

Lines 190 and 372 should use "GitHub" (capitalized) instead of "github". GitHub is a proper noun and must always be capitalized for consistency with official branding and documentation standards.

Also applies to: 372-372

⛔ Skipped due to learnings
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 586
File: webhook_server_container/libs/github_api.py:1947-1956
Timestamp: 2024-10-08T09:19:56.185Z
Learning: In `webhook_server_container/libs/github_api.py`, the indentation style used in the `set_pull_request_automerge` method is acceptable as per the project's coding standards.
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Handler pattern: implement `__init__(self, github_webhook: GithubWebhook, ...)` and `process_event(event_data: dict) -> None` for all GitHub event handlers
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Architecture guarantee: `repository_data` is ALWAYS set before handlers instantiate in `GithubWebhook.process()` with fail-fast exceptions - no defensive checks needed
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Maintain backward compatibility for webhook payload handling - must follow GitHub webhook specification to ensure compatibility with GitHub API changes
Learnt from: CR
Repo: myk-org/github-webhook-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T12:39:43.079Z
Learning: Applies to webhook_server/libs/handlers/**/*.py : Use `self.github_webhook.unified_api` for all GitHub API operations in handlers - never access GitHub API directly
Learnt from: myakove
Repo: myk-org/github-webhook-server PR: 588
File: webhook_server_container/libs/github_api.py:1632-1637
Timestamp: 2024-10-14T14:13:21.316Z
Learning: In the `ProcessGithubWehook` class in `webhook_server_container/libs/github_api.py`, avoid using environment variables to pass tokens because multiple commands with multiple tokens can run at the same time.
webhook_server/tests/test_edge_cases_validation.py (3)

9-11: LGTM - Proper imports for async type hints

The added imports (AsyncIterator, Any) correctly support the async generator type hints and generic dict annotations required for mypy strict mode compatibility.


712-747: Excellent async conversion with complete type hints

The test has been properly converted to async with all the previously flagged issues addressed:

  • async_iter_empty() has explicit AsyncIterator[LogEntry] return type
  • Uses side_effect=lambda *_, **__: async_iter_empty() to create fresh generators per call
  • All controller method calls properly awaited
  • Type hints fully compatible with mypy strict mode

The if False: yield pattern is intentional to create an explicit empty async generator, as discussed in previous reviews.


749-794: Well-structured large dataset test with proper async patterns

The test correctly:

  • Converts to async with complete type hints (AsyncIterator[LogEntry])
  • Uses fresh generator instances via side_effect=lambda
  • Awaits all async controller methods
  • Tests appropriate edge cases: large datasets (100k entries), pagination limits, and export size constraints
  • Handles HTTPException with status 413 for oversized exports per the controller's contract
webhook_server/utils/app_utils.py (1)

126-221: LGTM! Explicit validation correctly implements fail-fast principle.

The implementation properly validates workflow step data before formatting:

  • Lines 182-190: Explicit checks for "status" and "duration_ms" presence, plus None check for duration_ms, raising ValueError with clear messages if validation fails.
  • Lines 191-193: Direct field access after validation ensures format_duration(step_duration_ms) never receives None.

This aligns perfectly with the coding guideline: "Never return fake default values ('', 0, False, None objects, []) to hide missing data - raise exceptions with clear error messages instead to enable fail-fast debugging."

The explicit validation approach surfaces instrumentation gaps immediately (e.g., if a handler forgets to call complete_step()/fail_step()), which is exactly what you want for debugging.

Static analysis notes (informational only, no action needed):

  • TRY003 warnings about long exception messages are acceptable here—the contextual details (step name, field name) are necessary for debugging.
  • G004 warning about f-strings in logging is a false positive—per project learnings, f-strings are the established style for log interpolation in this codebase.
webhook_server/tests/test_log_viewer.py (1)

1-1217: LGTM! Comprehensive test coverage with past review items addressed.

This test suite provides excellent coverage of JSON log streaming, log retrieval, export, workflow steps, and edge cases. All past review comments have been properly addressed:

Fixture alignment (lines 85-101): Timing schema now uses completed_at and duration_ms, matching production structure
Type annotations: Mock helper functions have complete type hints (e.g., async def mock_close(code: int, reason: str) -> None)
Async best practices: time.sleep replaced with await asyncio.sleep to avoid blocking event loop
Meaningful assertions: No-op checks like len(entries) >= 0 replaced with actual validations

Regarding ASYNC230 warnings (informational, no changes needed):
The static analyzer flags blocking open() calls in async test methods. These warnings are acceptable because:

  • File I/O occurs during test setup (creating fixtures), not during async test execution
  • Test data files are tiny and write operations are fast
  • The actual async test logic (streaming, iteration) correctly uses async patterns
  • Coding guidelines mandate asyncio.to_thread() for PyGithub calls and production code—test fixture setup is explicitly exempt from this requirement

The test structure is clean, readable, and provides robust validation of the JSON-first log viewer functionality.

webhook_server/tests/test_log_parser.py (1)

1-1379: LGTM! All past review items addressed, excellent test coverage added.

This file adds comprehensive JSON log parsing tests while properly addressing all previous review concerns:

Type hints (lines 274-279): _collect_entries helper now has complete annotations:

async def _collect_entries(
    self,
    async_gen: AsyncIterator[LogEntry],
    entries_list: list[LogEntry],
    max_entries: int = 10,
) -> None:

UTC-aware datetimes (lines 1269, 1276, 1283, 1302, 1309, 1316): All datetime.datetime() calls now include tzinfo=datetime.UTC, resolving DTZ001 warnings and ensuring consistency with production logging.

Non-blocking I/O in async tests (lines 252-258, 1361-1366): File append operations properly wrapped with asyncio.to_thread():

def _append_entries() -> None:
    with open(f.name, "a") as append_f:
        # ... write operations
        
await asyncio.to_thread(_append_entries)

New test coverage highlights:

  • TestJSONLogParsing (lines 772-1192): 30+ tests validating JSON log entry parsing, including valid/invalid JSON, timestamp handling, PR extraction, error scenarios, and timezone formats
  • TestAdditionalCoverageTests (lines 1194-1379): Edge cases for parsing (invalid PR numbers, escaped brackets), filtering (unique values, counts), token spend extraction, and directory monitoring with rotated files

The test suite now provides robust validation of both legacy text log parsing and new JSON-first parsing paths, ensuring backward compatibility while enabling the structured logging migration.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 5, 2026
@myakove myakove merged commit 91c37bf into main Jan 5, 2026
7 of 9 checks passed
@myakove myakove deleted the feature/structured-json-logging branch January 5, 2026 14:43
@myakove-bot
Copy link
Collaborator

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

rnetser added a commit that referenced this pull request Jan 6, 2026
Add try-except wrapper to label_pull_request_by_merge_state with
ctx.fail_step() to match PR #968 structured logging patterns.
Also fix log message format.
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