Skip to content

refactor: complete project-wide migration to structured logging#52

Open
dkargatzis wants to merge 1 commit intomainfrom
fix/code-quality-improvements
Open

refactor: complete project-wide migration to structured logging#52
dkargatzis wants to merge 1 commit intomainfrom
fix/code-quality-improvements

Conversation

@dkargatzis
Copy link
Member

@dkargatzis dkargatzis commented Feb 26, 2026

  • Migrate all remaining standard logging calls to structlog
  • Convert f-string interpolations into kwargs for structured log payloads
  • Remove unused variables and fix enum types in core models
  • Ensure 100% test coverage stability post-refactor

Summary by CodeRabbit

  • Refactor
    • Migrated logging to structured, key/value output for improved observability and consistent logs
    • Standardized log messages across the app for clearer telemetry and troubleshooting
  • Style
    • Switched several enum types to a more robust string-backed implementation for clearer typing
  • Chores
    • Minor internal typing and lifecycle annotations updated; no user-facing behavior or public API changes

@dkargatzis dkargatzis self-assigned this Feb 26, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR standardizes logging across the codebase by replacing Python's stdlib logging with structlog, converting log calls to structured key/value events. It also migrates several Enum subclasses to StrEnum and updates a few async type hints (lifespan/log_operation) and adds a RuleCreationProcessor init.

Changes

Cohort / File(s) Summary
Agents (core & tests)
src/agents/base.py, src/agents/factory.py, src/agents/acknowledgment_agent/agent.py, src/agents/acknowledgment_agent/test_agent.py, src/agents/engine_agent/agent.py, src/agents/engine_agent/models.py, src/agents/engine_agent/nodes.py
Replaced module logging with structlog.get_logger() and converted log messages to structured key/value events. Switched ValidationStrategy to StrEnum. No API signature changes.
Feasibility agent
src/agents/feasibility_agent/agent.py, src/agents/feasibility_agent/nodes.py
Migrated to structlog with structured fields for initialization, analysis, YAML generation, retries and error reporting.
Core utilities & runtime
src/core/utils/logging.py, src/core/utils/caching.py, src/core/utils/metrics.py, src/core/utils/retry.py, src/core/utils/timeout.py
Logging moved to structlog; log_operation annotated to return AsyncIterator[None]; structured lifecycle and function-call logs added.
Core models & main
src/core/models.py, src/main.py
Converted EventType to StrEnum; added AsyncIterator annotation for lifespan; logging migrated to structlog and event registrations use enum .value.
Event processors & webhooks
src/event_processors/..., src/webhooks/handlers/deployment_review.py, src/webhooks/handlers/deployment_status.py, src/webhooks/handlers/issue_comment.py, src/event_processors/rule_creation.py
Swapped to structlog across processors and webhook handlers; replaced free-form logs with structured events and fields. Added RuleCreationProcessor.__init__ to initialize feasibility agent; WebhookResponse usages updated accordingly.
Rules & loaders
src/rules/models.py, src/rules/acknowledgment.py, src/rules/loaders/github_loader.py, src/rules/registry.py, src/rules/conditions/..., src/rules/utils/*
Migrated logging to structlog and standardized event keys; changed RuleSeverity and RuleCategory to StrEnum.
Integrations & presentation
src/integrations/github/check_runs.py, src/presentation/github_formatter.py
Structured logging for GitHub check run flows and formatter initialization; log payloads now include repo/sha/conclusion fields.
Processors & PR handling
src/event_processors/pull_request/processor.py, src/event_processors/pull_request/enricher.py, src/event_processors/push.py, src/event_processors/deployment_*.py, src/event_processors/check_run.py, src/event_processors/violation_acknowledgment.py
All PR/push/deployment processors updated to structlog with structured keys, preserving control flow and return shapes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Matas/fix/fix actions #43 — Applies a similar structlog migration across overlapping modules (logger initialization and structured log events).

Poem

🐰 I hopped through code with careful paws,
Swapped strings for keys and tidy laws.
Logs now whisper fields in JSON light,
No more emojis jumping in sight.
Hooray — structured hops into the night! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'refactor: complete project-wide migration to structured logging' directly and clearly describes the main objective of the changeset, which is a comprehensive migration from standard logging to structlog across the entire project.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/code-quality-improvements

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.

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (14)
src/rules/acknowledgment.py (1)

113-128: ⚠️ Potential issue | 🟠 Major

Avoid logging raw comment content and include required boundary fields.

Line 113 currently logs full comment_body (external/user text), and Lines 113/122/126/128 omit the required structured boundary fields. This creates privacy risk and inconsistent observability.

As per coding guidelines, "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms" and "Strip secrets/PII ... keep raw reasoning out of logs (store summaries only)".

Proposed fix
+from time import perf_counter
 import re
 from enum import StrEnum

 import structlog
@@
 def extract_acknowledgment_reason(comment_body: str) -> str:
@@
-    logger.info("extracting_acknowledgment_reason_from", comment_body=comment_body)
+    started_at = perf_counter()
+    logger.info(
+        "acknowledgment_reason_extraction_started",
+        operation="extract_acknowledgment_reason",
+        subject_ids=[],
+        decision="started",
+        latency_ms=0,
+        comment_length=len(comment_body),
+    )

     for i, pattern in enumerate(ACKNOWLEDGMENT_PATTERNS):
         match = re.search(pattern, comment_body, re.IGNORECASE | re.DOTALL)
         if match:
@@
-            logger.info("pattern_matched_reason", reason=reason)
+            logger.info(
+                "acknowledgment_reason_pattern_matched",
+                operation="extract_acknowledgment_reason",
+                subject_ids=[],
+                decision="matched",
+                latency_ms=round((perf_counter() - started_at) * 1000, 2),
+                pattern_index=i,
+                reason_length=len(reason),
+            )
             if reason:
                 return reason
         else:
-            logger.debug("pattern_did_not_match")
+            logger.debug(
+                "acknowledgment_reason_pattern_not_matched",
+                operation="extract_acknowledgment_reason",
+                subject_ids=[],
+                decision="not_matched",
+                latency_ms=round((perf_counter() - started_at) * 1000, 2),
+                pattern_index=i,
+            )

-    logger.info("no_patterns_matched_for_acknowledgment_reason")
+    logger.info(
+        "acknowledgment_reason_not_found",
+        operation="extract_acknowledgment_reason",
+        subject_ids=[],
+        decision="no_match",
+        latency_ms=round((perf_counter() - started_at) * 1000, 2),
+    )
     return ""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/acknowledgment.py` around lines 113 - 128, The code currently logs
raw comment_body and omits required structured boundary fields; update all
logger calls in this block (the initial logger.info, the
logger.info("pattern_matched_reason"), logger.debug, and final logger.info) to
stop emitting raw user text and instead log only a sanitized summary/flag (e.g.,
has_reason: true/false) and include the mandated structured fields: operation
(e.g., "extract_ack_reason"), subject_ids (pass the relevant IDs or an empty
list), decision (e.g., "ack_extracted" or "no_ack"), and latency_ms (measure
elapsed time around the loop); when logging the extracted reason variable from
ACKNOWLEDGMENT_PATTERNS, do not log the full reason—log a length-limited/hashed
summary or a boolean has_reason and reason_summary—while preserving use of the
existing ACKNOWLEDGMENT_PATTERNS and reason variable names so the changes are
localized.
src/core/utils/caching.py (1)

60-65: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: Line 62 still uses f-string interpolation.

The expiration log on line 62 wasn't converted to structured format, inconsistent with the PR objective and the adjacent line 65.

🔧 Proposed fix
         if age >= self.ttl:
             del self._cache[key]
-            logger.debug(f"Cache entry '{key}' expired (age: {age:.2f}s, ttl: {self.ttl}s)")
+            logger.debug("cache_entry_expired", key=key, age_seconds=round(age, 2), ttl_seconds=self.ttl)
             return None

         logger.debug("cache_hit_for", key=key)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/caching.py` around lines 60 - 65, The expiration log still
uses an f-string; update the logger call in the cache expiration branch (inside
the method that checks age vs self.ttl) to a structured log like the adjacent
cache hit line by passing a message key and the fields (e.g., key, age, ttl) to
logger.debug instead of f-string interpolation so the log is consistent with
structured logging for the cache (refer to logger.debug, self.ttl, key, age, and
self._cache in the expiration branch).
src/rules/loaders/github_loader.py (1)

42-76: ⚠️ Potential issue | 🟠 Major

Complete the structured log envelope at boundaries (and remove remaining f-string logs).

Line 42 and Line 70 still use interpolated strings, and boundary logs in get_rules are missing required fields (operation, subject_ids, decision, latency_ms).

Proposed patch (pattern)
+import time
 import structlog
 import yaml
@@
     async def get_rules(self, repository: str, installation_id: int) -> list[Rule]:
+        started_at = time.perf_counter()
+        log_ctx = {
+            "operation": "github_rule_loader.get_rules",
+            "subject_ids": {"repository": repository, "installation_id": installation_id},
+        }
         try:
@@
-            logger.info(f"Fetching rules for repository: {repository} (installation: {installation_id})")
+            logger.info("fetch_rules_start", **log_ctx, decision="start", latency_ms=0)
@@
-                logger.warning("no_rulesyaml_file_found_in", repository=repository)
+                logger.warning(
+                    "no_rulesyaml_file_found_in",
+                    **log_ctx,
+                    decision="missing_rules_file",
+                    latency_ms=int((time.perf_counter() - started_at) * 1000),
+                )
@@
-            logger.info(f"Successfully loaded {len(rules)} rules from {repository}")
+            logger.info(
+                "fetch_rules_success",
+                **log_ctx,
+                decision="success",
+                rules_count=len(rules),
+                latency_ms=int((time.perf_counter() - started_at) * 1000),
+            )
             return rules
@@
-            logger.error("error_fetching_rules_for", repository=repository, e=e)
+            logger.error(
+                "error_fetching_rules_for",
+                **log_ctx,
+                decision="error",
+                error=str(e),
+                latency_ms=int((time.perf_counter() - started_at) * 1000),
+            )
             raise

As per coding guidelines: Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms.

Also applies to: 92-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/loaders/github_loader.py` around lines 42 - 76, Replace the
remaining f-string boundary logs and add the required structured fields in
get_rules of GitHubRuleLoader: wrap the fetch with a start timestamp
(time.monotonic()) and replace logger.info(f"...") at the start with a
structured logger call like logger.info("fetching_rules", operation="get_rules",
subject_ids=[repository], decision="pending", latency_ms=None,
installation_id=installation_id); on successful return replace
logger.info(f"Successfully loaded...") with logger.info("fetched_rules",
operation="get_rules", subject_ids=[repository], decision="loaded",
latency_ms=calculated_ms, count=len(rules)); update error/exception logs (the
rule parse error and the outer exception) to include operation="get_rules",
subject_ids=[repository], decision="error", latency_ms=calculated_ms and pass
exception info via exc_info=True (or structured field like error=str(e)) instead
of using f-strings; remove any remaining f-strings in this method.
src/event_processors/check_run.py (1)

39-43: ⚠️ Potential issue | 🟠 Major

Check-run boundary logs are still only partially structured.

This path should emit boundary events with operation, subject_ids, decision, and completion latency_ms. Current mixed style makes tracing and policy compliance inconsistent.

Suggested refactor
-        logger.info("processing_check_run_event_for", repo_full_name=task.repo_full_name)
-        logger.info(f"   Name: {check_run.get('name')}")
-        logger.info(f"   Status: {check_run.get('status')}")
-        logger.info(f"   Conclusion: {check_run.get('conclusion')}")
+        logger.info(
+            "check_run_processing_started",
+            operation="process_check_run",
+            subject_ids={"repo_full_name": task.repo_full_name, "check_run_id": check_run.get("id")},
+            decision="accepted",
+            check_run_name=check_run.get("name"),
+            status=check_run.get("status"),
+            conclusion=check_run.get("conclusion"),
+        )
@@
-            logger.error("no_installation_id_found_in_task")
+            logger.error(
+                "check_run_processing_rejected",
+                operation="process_check_run",
+                subject_ids={"repo_full_name": task.repo_full_name},
+                decision="rejected",
+                reason="missing_installation_id",
+            )
As per coding guidelines "Use structured logging at boundaries with fields: `operation`, `subject_ids`, `decision`, `latency_ms`".

Also applies to: 57-57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/check_run.py` around lines 39 - 43, Replace the mixed,
human-friendly logs with a single structured boundary log: remove the four
logger.info lines that print repo/name/status/conclusion and instead emit one
logger.info call with fields operation="check_run.process" (or
"check_run.process_completion" if this is the end-of-flow), subject_ids set to a
list including task.repo_full_name and the check_run id (e.g.
check_run.get("id")), decision set to check_run.get("conclusion"), and
latency_ms computed from the task's start/timestamp (e.g. time.monotonic() -
task.start_time or task.created_at) converted to milliseconds; apply the same
replacement to the similar log at the later location (the one noted at line 57)
so all boundary events use the same structured fields.
src/rules/utils/validation.py (1)

20-31: ⚠️ Potential issue | 🔴 Critical

Remove src/rules/utils.py as dead code; it's replaced by src/rules/utils/validation.py which is the canonical, properly-exported version.

Three copies of validate_rules_yaml_from_repo exist in the codebase: src/rules/utils.py, src/rules/utils/validation.py, and src/integrations/github/rules_service.py. Only src/rules/utils/validation.py is actually imported (via src/rules/utils/__init__.py); the other two are unused.

Additionally, src/integrations/github/rules_service.py violates structured logging guidelines at lines 25 and 109 by using f-strings instead of keyword arguments in logger calls.

Delete src/rules/utils.py and either remove or refactor src/integrations/github/rules_service.py to avoid maintenance burden and ensure compliance with logging standards.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils/validation.py` around lines 20 - 31, Delete the dead
duplicate module src/rules/utils.py and ensure all imports refer to the
canonical src/rules/utils/validation.py (which defines
validate_rules_yaml_from_repo); then open
src/integrations/github/rules_service.py, remove or delegate its duplicate
validate_rules_yaml_from_repo implementation to the canonical function, and
replace the two logger f-string usages (the calls around the previously noted
lines) with structured logging calls using keyword args, e.g.
logger.info("message", pr_number=pr_number, repo_full_name=repo_full_name) so
all logging conforms to the project's structured logging style.
src/rules/conditions/filesystem.py (1)

188-188: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string log still present.

This log statement still uses f-string interpolation, which contradicts the PR objective to convert all f-string interpolations into kwargs for structured log payloads. The same issue exists at lines 213, 215, and 261.

Proposed fix
-            logger.debug(f"MaxFileSizeCondition: All {len(files)} files are within size limit of {max_size_mb}MB")
+            logger.debug("maxfilesizecondition_all_files_within_limit", file_count=len(files), max_size_mb=max_size_mb)

Similar changes needed for lines 213, 215, and 261.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/conditions/filesystem.py` at line 188, The f-string in the
logger.debug call inside MaxFileSizeCondition should be converted to a
structured log call: replace logger.debug(f"...{len(files)}...{max_size_mb}...")
with a non-f-string message and supply the dynamic values as kwargs (e.g.,
logger.debug("MaxFileSizeCondition: All files are within size limit",
files=len(files), max_size_mb=max_size_mb)); apply the same conversion pattern
to the analogous logger.debug calls referenced at the other occurrences (lines
with the same MaxFileSizeCondition context) so all structured logs use kwargs
instead of f-strings.
src/rules/utils/contributors.py (1)

72-72: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string log still present.

This log statement still uses f-string interpolation, inconsistent with the structured logging migration goal of this PR.

Proposed fix
-            logger.info(f"Found {len(past_contributors)} past contributors for {repo}")
+            logger.info("found_past_contributors_for", count=len(past_contributors), repo=repo)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils/contributors.py` at line 72, Replace the f-string log in
contributors.py (the logger.info line that references past_contributors and
repo) with a structured log call: call logger.info with a static message and
pass the dynamic values as structured fields (e.g., logger.info("Found past
contributors", past_contributors_count=len(past_contributors), repo=repo)) so
the message no longer uses f-string interpolation and adheres to the structured
logging migration.
src/event_processors/push.py (1)

85-85: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string logs still present.

Several log statements still use f-string interpolation (lines 39, 85, 149, 150), which is inconsistent with the PR's structured logging migration objective.

Proposed fix for line 85
-        logger.info(f"📋 Loaded {len(rules)} rules for evaluation")
+        logger.info("loaded_rules_for_evaluation", count=len(rules))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/push.py` at line 85, The log at
src/event_processors/push.py uses an f-string: replace logger.info(f"📋 Loaded
{len(rules)} rules for evaluation") with structured logging by calling
logger.info("Loaded rules for evaluation", rules_count=len(rules)) or
logger.info("Loaded %d rules for evaluation", len(rules)); update the same
pattern for the other f-string logs mentioned (lines with logger and variables
at lines 39, 149, 150) so all logging uses structured/positional parameters
instead of f-strings (look for the logger variable and the rules/related
variables to change).
src/core/utils/retry.py (1)

65-68: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string with emoji still present.

This warning log still uses f-string interpolation with emoji, inconsistent with the structured migration applied to the error log at line 61 in the same function.

Proposed fix
-                    logger.warning(
-                        f"⚠️ {func.__name__} attempt {attempt + 1}/{max_retries} failed, "
-                        f"retrying in {wait_time:.2f}s: {e}"
-                    )
+                    logger.warning(
+                        "attempt_failed_retrying_in_s",
+                        func_name=func.__name__,
+                        attempt=attempt + 1,
+                        max_retries=max_retries,
+                        wait_time_s=wait_time,
+                        error=str(e),
+                    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/retry.py` around lines 65 - 68, Replace the f-string emoji log
in the retry handler with a structured logging call consistent with the earlier
error log change: remove the emoji and stop f-string interpolation in the
logger.warning call (the call that references func.__name__, attempt,
max_retries, wait_time, and e), and pass those values as separate positional
arguments (and/or use exc_info=True if the previous error log used it) so the
message becomes a format string plus arguments rather than an f-string; mirror
the pattern used for the error log at the other logger call in the same function
to keep logging consistent.
src/event_processors/deployment_protection_rule.py (1)

136-136: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: %-format string still present.

This log and similar ones at lines 169, 171, and 218 still use %-style or f-string formatting, which is inconsistent with the PR's structured logging migration objective.

Proposed fix
-            logger.info("Found %d applicable rules for deployment", len(deployment_rules))
+            logger.info("found_applicable_rules_for_deployment", count=len(deployment_rules))

Apply similar changes to lines 169, 171, and 218.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/deployment_protection_rule.py` at line 136, Replace the
%-style/inlined-format logs with structured-logging calls: for each occurrence
(the logger.info that mentions deployment_rules at lines referencing the
variable deployment_rules and the other two similar messages), stop using
formatted message strings and instead log a constant message plus structured
fields via the logger's extra (e.g. extra={"deployment_rules_count":
len(deployment_rules), "rule_ids": [...]}) or by passing a dict/kwargs to the
logger; locate and update uses around the variable deployment_rules and any
other rule/count variables in this module (deployment_protection_rule.py) so
messages like "Found %d applicable rules for deployment" become something like
"Found applicable rules for deployment" with the counts and identifiers in extra
fields for consistent structured logging across the file.
src/agents/engine_agent/agent.py (2)

136-136: ⚠️ Potential issue | 🟡 Minor

Additional f-string to convert.

Proposed fix
-            logger.info(f"🔧 Rule Engine extracted {len(violations)} violations")
+            logger.info("rule_engine_violations_extracted", count=len(violations))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/agent.py` at line 136, Replace the f-string usage in
the logger call so it uses structured logging instead of an f-string; change the
line that calls logger.info(f"🔧 Rule Engine extracted {len(violations)}
violations") to use logger.info with a format string and parameters (e.g.,
logger.info("🔧 Rule Engine extracted %d violations", len(violations))) or
logger.info("🔧 Rule Engine extracted {} violations".format(len(violations)));
this targets the logger.info call that references the violations variable in the
same scope.

99-99: ⚠️ Potential issue | 🟡 Minor

Additional f-string to convert.

Proposed fix
-            logger.info(f"🔧 Rule Engine starting evaluation for {request.event_type} with {len(request.rules)} rules")
+            logger.info(
+                "rule_engine_starting_evaluation",
+                event_type=request.event_type,
+                rule_count=len(request.rules),
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/agent.py` at line 99, Replace the f-string used in
the logger.info call so it follows the project's non-f-string logging style:
change the logger.info(f"🔧 Rule Engine starting evaluation for
{request.event_type} with {len(request.rules)} rules") to a non-f-string form
that passes values as parameters (e.g., logger.info("🔧 Rule Engine starting
evaluation for %s with %d rules", request.event_type, len(request.rules))) or
use .format on the string; update the call where logger.info is invoked in the
Rule Engine evaluation path that references request.event_type and
len(request.rules).
src/agents/acknowledgment_agent/test_agent.py (1)

90-94: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-strings still used in log statements.

These lines still use f-string interpolation instead of structured kwargs.

Proposed fix
-            logger.info(f"   Valid: {result.data.get('is_valid', False)}")
-            logger.info(f"   Reasoning: {result.data.get('reasoning', 'No reasoning')}")
-            logger.info(f"   Acknowledged violations: {len(result.data.get('acknowledgable_violations', []))}")
-            logger.info(f"   Require fixes: {len(result.data.get('require_fixes', []))}")
-            logger.info(f"   Confidence: {result.data.get('confidence', 0.0)}")
+            logger.info(
+                "acknowledgment_result",
+                is_valid=result.data.get("is_valid", False),
+                reasoning=result.data.get("reasoning", "No reasoning"),
+                acknowledged_count=len(result.data.get("acknowledgable_violations", [])),
+                require_fixes_count=len(result.data.get("require_fixes", [])),
+                confidence=result.data.get("confidence", 0.0),
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/test_agent.py` around lines 90 - 94, Replace
the f-string log statements in test_agent.py with structured logging calls: use
logger.info with a constant message and pass values as separate parameters or as
structured kwargs (reference the logger variable and the result.data keys
is_valid, reasoning, acknowledgable_violations, require_fixes, confidence) —
e.g., change logger.info(f"   Valid: {result.data.get('is_valid', False)}") to a
structured form that supplies the boolean via arguments or named fields, and do
the same for the "Reasoning", "Acknowledged violations", "Require fixes", and
"Confidence" lines so no f-strings remain.
src/agents/engine_agent/nodes.py (1)

37-48: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-strings still used in multiple log statements.

These log statements still use f-string interpolation instead of structured kwargs. This is inconsistent with the PR objective to convert f-strings to kwargs.

Proposed fix for this segment
-        logger.info(f"🔍 Analyzing {len(state.rule_descriptions)} rule descriptions")
+        logger.info("analyzing_rule_descriptions", count=len(state.rule_descriptions))

         # Filter rules applicable to this event type
         applicable_rules = []
         for rule_desc in state.rule_descriptions:
             if state.event_type in rule_desc.event_types:
                 applicable_rules.append(rule_desc)
-                logger.info(f"🔍 Rule '{rule_desc.description[:50]}...' is applicable to {state.event_type}")
+                logger.debug("rule_applicable", rule=rule_desc.description[:50], event_type=state.event_type)
             else:
-                logger.info(
-                    f"🔍 Rule '{rule_desc.description[:50]}...' is NOT applicable (expects: {rule_desc.event_types})"
-                )
+                logger.debug("rule_not_applicable", rule=rule_desc.description[:50], expected_types=rule_desc.event_types)

Note: Similar f-string patterns remain throughout the file (lines 71, 84, 93, 126, 131, 155, 169-170, 182, 226, 251, 259-261, 312, 351, 383, 404).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/nodes.py` around lines 37 - 48, Replace the f-string
log calls with structured logging calls: use logger.info with a format string
and positional parameters (e.g. logger.info("🔍 Analyzing %d rule descriptions",
len(state.rule_descriptions))) or with explicit kwargs if your logger supports
them (e.g. logger.info("🔍 Rule '%s...' is applicable to %s",
rule_desc.description[:50], state.event_type) or logger.info("🔍 Rule
applicable", rule_description=rule_desc.description[:50],
event_type=state.event_type)); update every occurrence in this file where
logger.info is called using f-strings (e.g. the block iterating
state.rule_descriptions referencing logger, state.rule_descriptions, rule_desc,
and state.event_type) and apply the same pattern to the other listed lines to
ensure structured kwargs/parameters are used instead of f-strings.
🟡 Minor comments (15)
src/event_processors/deployment_status.py-52-52 (1)

52-52: ⚠️ Potential issue | 🟡 Minor

Inconsistent: f-string still used instead of structured kwargs.

This log statement still uses f-string interpolation, which contradicts the PR objective to convert all f-string interpolations to kwargs for structured log payloads. Per coding guidelines, boundary logs should include fields like operation and latency_ms.

Proposed fix
-        logger.info(f"🏁 DEPLOYMENT_STATUS processing completed in {int((time.time() - start_time) * 1000)}ms")
+        logger.info(
+            "deployment_status_processing_completed",
+            operation="deployment_status",
+            latency_ms=int((time.time() - start_time) * 1000),
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/deployment_status.py` at line 52, Replace the f-string
in the logger call so it uses structured kwargs instead of interpolation:
compute latency_ms = int((time.time() - start_time) * 1000) and call logger.info
with a static message (e.g. "🏁 DEPLOYMENT_STATUS processing completed") plus
keyword fields operation="DEPLOYMENT_STATUS" and latency_ms=latency_ms (and
include any existing context if present), updating the logger.info invocation
used in deployment_status.py to pass these kwargs rather than an f-string.
src/rules/utils/codeowners.py-201-203 (1)

201-203: ⚠️ Potential issue | 🟡 Minor

Improve exception logging for better serialization.

Passing the raw exception object as e=e may not serialize cleanly in all structlog configurations. Use explicit string conversion and consider adding stack trace context:

     except Exception as e:
-        logger.error("error_loading_codeowners_file", e=e)
+        logger.error("error_loading_codeowners_file", operation="load_codeowners", error=str(e), exc_info=True)
         return None

This ensures:

  • error=str(e) provides a serializable error message
  • exc_info=True captures the stack trace for debugging
  • operation field aligns with boundary logging requirements
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils/codeowners.py` around lines 201 - 203, The exception logging
in the except block currently passes the raw exception object
(logger.error("error_loading_codeowners_file", e=e)); change it to log a
serializable string and include the stack trace and an operation field: call
logger.error with a descriptive message key, error=str(e), exc_info=True and
operation="load_codeowners_file" (or similar) so the error text is serializable
and the stack trace is captured for debugging.
src/core/utils/caching.py-178-182 (1)

178-182: ⚠️ Potential issue | 🟡 Minor

Avoid using __name__ as a log field key.

Using dunder names (__name__) as keyword arguments is unconventional and may cause confusion with Python's built-in module attribute. Use a clearer field name.

🔧 Proposed fix
             if cached_value is not None:
-                logger.debug("cache_hit_for_with_key", __name__=func.__name__, cache_key=cache_key)
+                logger.debug("cache_hit", func_name=func.__name__, cache_key=cache_key)
                 return cached_value

             # Cache miss - execute function
-            logger.debug("cache_miss_for_with_key", __name__=func.__name__, cache_key=cache_key)
+            logger.debug("cache_miss", func_name=func.__name__, cache_key=cache_key)
             result = await func(*args, **kwargs)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/caching.py` around lines 178 - 182, The log calls in
caching.py use the dunder keyword "__name__" as a log field which is confusing;
change both logger.debug invocations that pass __name__=func.__name__ (the cache
hit and cache miss logs) to use a clear field name such as "func_name" or
"function" (e.g., func_name=func.__name__) and keep cache_key as-is so logs show
func_name and cache_key consistently for cache_hit_for_with_key and
cache_miss_for_with_key.
src/agents/feasibility_agent/nodes.py-125-126 (1)

125-126: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string log on line 126.

Line 126 still uses f-string interpolation instead of structured kwargs.

Proposed fix
         logger.info("yaml_configuration_generated_for_rule_type", rule_type=state.rule_type)
-        logger.info(f"🔧 Generated YAML length: {len(state.yaml_content)} characters")
+        logger.info("yaml_generated", yaml_length=len(state.yaml_content))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/feasibility_agent/nodes.py` around lines 125 - 126, Replace the
f-string logging on the YAML length with structured logging: locate the
logger.info call near logger.info("yaml_configuration_generated_for_rule_type",
rule_type=state.rule_type) in nodes.py (the place that references
state.yaml_content) and change the f-string logger.info(f"🔧 Generated YAML
length: {len(state.yaml_content)} characters") to a structured call that passes
the length as a kwarg (e.g., logger.info("generated_yaml_length",
length=len(state.yaml_content)) or similar) so no f-string interpolation is
used.
src/agents/feasibility_agent/nodes.py-58-61 (1)

58-61: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string log on line 61.

Line 61 still uses f-string interpolation, which contradicts the PR objective to convert all f-strings into kwargs. Additionally, per coding guidelines, structured logs at boundaries should include fields like operation and latency_ms.

Proposed fix
         logger.info("rule_feasibility_analysis_completed", is_feasible=state.is_feasible)
         logger.info("rule_type_identified", rule_type=state.rule_type)
         logger.info("confidence_score", confidence_score=state.confidence_score)
-        logger.info(f"🔍 Analysis steps: {len(state.analysis_steps)} steps")
+        logger.info("analysis_steps_completed", step_count=len(state.analysis_steps))

Based on learnings: "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/feasibility_agent/nodes.py` around lines 58 - 61, Replace the
f-string logger call that logs analysis steps with a structured logger.info
call: compute steps_count = len(state.analysis_steps) and call
logger.info("rule_analysis_steps", steps_count=steps_count,
operation="rule_feasibility_analysis", subject_ids=getattr(state, "subject_ids",
None), decision=state.is_feasible, latency_ms=getattr(state, "latency_ms",
None)); update the nearby logging lines
(logger.info("rule_feasibility_analysis_completed", ...),
logger.info("rule_type_identified", ...), logger.info("confidence_score", ...))
to follow the same structured pattern if needed so all boundary logs use kwargs
rather than f-strings and include operation/subject_ids/decision/latency_ms.
src/agents/feasibility_agent/nodes.py-122-122 (1)

122-122: ⚠️ Potential issue | 🟡 Minor

Truncated log message and missing context.

The log key generated_yaml_seems_too_short_may appears truncated ("may" at the end is incomplete). Include the actual length for debugging context.

Proposed fix
-            logger.warning("generated_yaml_seems_too_short_may")
+            logger.warning("generated_yaml_too_short", yaml_length=len(state.yaml_content))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/feasibility_agent/nodes.py` at line 122, The log message key
logger.warning("generated_yaml_seems_too_short_may") is truncated and lacks
context; update the call to use a complete message (e.g.,
"generated_yaml_seems_too_short_maybe" or "generated_yaml_too_short") and
include the actual length of the YAML string for debugging by passing the length
(e.g., len(generated_yaml) or the variable holding the YAML) in the log message
so the warning shows both the descriptive text and the numeric length.
src/event_processors/violation_acknowledgment.py-60-64 (1)

60-64: ⚠️ Potential issue | 🟡 Minor

Incomplete migration and potential PII in logs.

Several issues in this logging block:

  1. Lines 60, 64 use visual separators ("=" * 80) which don't add value in structured logging systems
  2. Line 63 still uses f-string interpolation instead of structured kwargs
  3. Line 63 logs user comment content which may contain PII

Per coding guidelines, strip PII from logs and use structured fields like operation, subject_ids.

♻️ Suggested refactor
-            logger.info("=" * 80)
-            logger.info("processing_violation_acknowledgment_for", repo=repo, pr_number=pr_number)
-            logger.info("commenter", commenter=commenter)
-            logger.info(f"    Comment: {comment_body[:100]}...")
-            logger.info("=" * 80)
+            logger.info(
+                "processing_violation_acknowledgment",
+                operation="violation_acknowledgment",
+                repo=repo,
+                pr_number=pr_number,
+                commenter=commenter,
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/violation_acknowledgment.py` around lines 60 - 64,
Remove the decorative separator logs and stop logging raw comment text; replace
the f-string logger call that mentions comment_body with a structured logger
call that uses kwargs (e.g., operation="violation_acknowledgment",
subject_ids={"repo": repo, "pr_number": pr_number}, actor=commenter) and either
omit comment_body entirely or log a redacted/hashed placeholder (e.g.,
comment_present=True or comment_hash) to avoid PII; update the three logger
calls that currently use "="*80, the "processing_violation_acknowledgment_for"
call, the "commenter" call, and the f-string line so all use structured fields
and include operation/subject_ids as shown.
src/rules/conditions/pull_request.py-142-142 (1)

142-142: ⚠️ Potential issue | 🟡 Minor

Formatter drift remains in updated logger calls.

Pre-commit is currently failing on formatting. Please run pre-commit run --all-files (or ruff format) and commit the result.

Also applies to: 210-210, 245-245

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/conditions/pull_request.py` at line 142, Formatting drift in the
updated logger.debug calls (e.g., the logger.debug(...) call in
src/rules/conditions/pull_request.py and the similar occurrences noted) is
causing pre-commit failures; run the formatter (pre-commit run --all-files or
ruff format) and commit the changes so the logger calls conform to project
style, and verify the three affected logger.debug lines (the one shown plus the
two at the other commented locations) are updated consistently by the formatter
before committing.
src/event_processors/rule_creation.py-63-63 (1)

63-63: ⚠️ Potential issue | 🟡 Minor

Event key embeds value - use a field instead.

The event key api_calls_made_1 embeds the value in the key name. This loses the benefit of structured logging where values should be in fields for querying and aggregation.

Proposed fix
-            logger.info("api_calls_made_1")
+            logger.info("api_calls_made", count=1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/rule_creation.py` at line 63, The log call in
rule_creation.py currently embeds the value in the event key
("api_calls_made_1"); change the logger.info invocation (the logger.info call at
that location) to emit a stable event name/message (e.g., "api_calls_made") and
pass the numeric value as a structured field (e.g., api_calls_made: 1) so
querying/aggregation can use the field instead of the key. Ensure you use the
logger's structured logging API (keyword args or an "extra"/"fields" dict
depending on your logger) and remove the hard-coded value from the message.
src/event_processors/push.py-43-43 (1)

43-43: ⚠️ Potential issue | 🟡 Minor

Inconsistent event key naming.

The event key pushskippeddeletedorempty lacks underscores, making it inconsistent with other keys like push_processing_completed_in_ms. Use a consistent naming convention.

Proposed fix
-            logger.info("pushskippeddeletedorempty")
+            logger.info("push_skipped_deleted_or_empty")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/push.py` at line 43, Replace the inconsistent event key
string "pushskippeddeletedorempty" with a snake_case key to match the project's
convention (e.g., "push_skipped_deleted_or_empty") wherever it's logged; update
the call to logger.info in src/event_processors/push.py (the logger.info(...)
that emits "pushskippeddeletedorempty") so it uses the new snake_case key and
ensure it aligns stylistically with existing keys like
"push_processing_completed_in_ms".
src/agents/acknowledgment_agent/agent.py-179-184 (1)

179-184: ⚠️ Potential issue | 🟡 Minor

Mixed logging styles and raw reasoning in logs.

  1. Lines 182-183 still use f-strings instead of structured kwargs
  2. Line 181 logs full LLM reasoning text, which may violate the guideline to "keep raw reasoning out of logs (store summaries only)"
Proposed fix
             logger.info("intelligent_evaluation_completed")
             logger.info("valid", is_valid=structured_result.is_valid)
-            logger.info("reasoning", reasoning=structured_result.reasoning)
-            logger.info(f"    Acknowledged violations: {len(acknowledgable_violations)}")
-            logger.info(f"    Require fixes: {len(require_fixes)}")
+            logger.info("evaluation_summary", acknowledged_count=len(acknowledgable_violations), require_fixes_count=len(require_fixes))
             logger.info("confidence", confidence=structured_result.confidence)

Based on learnings: "Strip secrets/PII from agent prompts; scope tools; keep raw reasoning out of logs (store summaries only)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/agent.py` around lines 179 - 184, The logging
block mixes f-strings and structured kwargs and also logs raw LLM reasoning
text; update the logger calls in the intelligent evaluation completion section
to use structured kwargs consistently (e.g., logger.info("valid",
is_valid=structured_result.is_valid) and logger.info("acknowledgement_summary",
acknowledged_count=len(acknowledgable_violations),
require_fixes_count=len(require_fixes))) and remove or replace the raw reasoning
log (logger.info("reasoning", reasoning=...)) with a sanitized/short summary
field (e.g., structured_result.reasoning_summary or a truncated/scrubbed
summary) to avoid storing full LLM reasoning or PII. Ensure you reference the
same symbols: logger, structured_result, acknowledgable_violations,
require_fixes, and intelligent_evaluation_completed.
src/agents/engine_agent/agent.py-51-53 (1)

51-53: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: f-string on line 52.

Line 52 still uses f-string interpolation while lines 51 and 53 use structured keys.

Proposed fix
         logger.info("rule_engine_agent_initializing")
-        logger.info(f"🔧 Available validators: {len(AVAILABLE_CONDITIONS)}")
+        logger.info("available_validators", count=len(AVAILABLE_CONDITIONS))
         logger.info("validation_strategy_hybrid_validators_llm_fallback")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/agent.py` around lines 51 - 53, Replace the f-string
logging on the rule engine agent init with a structured log entry consistent
with the other lines: instead of logger.info(f"🔧 Available validators:
{len(AVAILABLE_CONDITIONS)}") use logger.info with a message key and a numeric
field (e.g., "available_validators") and pass len(AVAILABLE_CONDITIONS) as the
value; update the call in agent.py near the existing
logger.info("rule_engine_agent_initializing") and
logger.info("validation_strategy_hybrid_validators_llm_fallback") lines so the
log uses structured arguments rather than f-string interpolation.
src/webhooks/handlers/deployment_status.py-31-33 (1)

31-33: ⚠️ Potential issue | 🟡 Minor

Incomplete migration: Line 33 still uses f-string interpolation.

Lines 31-32 correctly use structured kwargs, but line 33 still uses an f-string. This should be converted to structured logging to complete the migration.

Proposed fix
     logger.info("enqueuing_deployment_status_event_for", repo_full_name=repo_full_name)
     logger.info("state", state=state)
-    logger.info(f"   Environment: {deployment.get('environment', 'unknown')}")
+    logger.info("deployment_environment", environment=deployment.get("environment", "unknown"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/webhooks/handlers/deployment_status.py` around lines 31 - 33, The log
call still uses an f-string; update the logger.info that prints the Environment
to use structured logging like the other calls: replace the f-string
logger.info(f"   Environment: {deployment.get('environment', 'unknown')}") with
a structured logger.info call that provides a message and passes the environment
value via a kwarg (use deployment.get('environment', 'unknown') as the value).
Locate this in the deployment status handler where logger.info is used alongside
repo_full_name and state.
src/main.py-63-63 (1)

63-63: ⚠️ Potential issue | 🟡 Minor

Inconsistent logging: still using standard logging instead of structlog.

This line uses logging.info() while the rest of the lifespan function (lines 87, 91, 97) uses the structlog logger.info(). This appears to be missed during the migration.

Proposed fix
-    logging.info("Watchflow application starting up...")
+    logger.info("watchflow_application_starting_up")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.py` at line 63, Replace the stray standard library call with the
structlog logger: change the logging.info("Watchflow application starting
up...") call to use the module-level structlog instance (logger.info(...)) so it
matches the other messages in the lifespan function (see logger and lifespan).
Also remove or adjust the unused standard logging import if it becomes unused
after this change.
src/agents/acknowledgment_agent/test_agent.py-100-105 (1)

100-105: ⚠️ Potential issue | 🟡 Minor

Additional f-strings to convert.

These lines also use f-string interpolation.

Proposed fix
             if result.data.get("acknowledgable_violations"):
                 logger.info("n_acknowledged_violations")
                 for violation in result.data["acknowledgable_violations"]:
-                    logger.info(f"   • {violation.get('rule_name')} - {violation.get('reason')}")
+                    logger.info("acknowledged_violation", rule_name=violation.get("rule_name"), reason=violation.get("reason"))

             if result.data.get("require_fixes"):
                 logger.info("n_violations_requiring_fixes")
                 for violation in result.data["require_fixes"]:
-                    logger.info(f"   • {violation.get('rule_name')} - {violation.get('reason')}")
+                    logger.info("violation_requiring_fix", rule_name=violation.get("rule_name"), reason=violation.get("reason"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/test_agent.py` around lines 100 - 105,
Replace the remaining f-string logger calls with parameterized logging calls:
change lines using logger.info(f"   • {violation.get('rule_name')} -
{violation.get('reason')}") to logger.info("   • %s - %s",
violation.get("rule_name"), violation.get("reason")) for both the initial
violations loop and the loop over result.data["require_fixes"]; keep the same
message text but use logger.info formatting rather than f-strings so the calls
in test_agent.py (the logger.info calls referencing violation and
result.data["require_fixes"]) no longer use f-string interpolation.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 267e19f and 4a6c671.

📒 Files selected for processing (44)
  • src/agents/acknowledgment_agent/agent.py
  • src/agents/acknowledgment_agent/test_agent.py
  • src/agents/base.py
  • src/agents/engine_agent/agent.py
  • src/agents/engine_agent/models.py
  • src/agents/engine_agent/nodes.py
  • src/agents/factory.py
  • src/agents/feasibility_agent/agent.py
  • src/agents/feasibility_agent/nodes.py
  • src/api/dependencies.py
  • src/core/models.py
  • src/core/utils/caching.py
  • src/core/utils/logging.py
  • src/core/utils/metrics.py
  • src/core/utils/retry.py
  • src/core/utils/timeout.py
  • src/event_processors/base.py
  • src/event_processors/check_run.py
  • src/event_processors/deployment.py
  • src/event_processors/deployment_protection_rule.py
  • src/event_processors/deployment_review.py
  • src/event_processors/deployment_status.py
  • src/event_processors/pull_request/enricher.py
  • src/event_processors/pull_request/processor.py
  • src/event_processors/push.py
  • src/event_processors/rule_creation.py
  • src/event_processors/violation_acknowledgment.py
  • src/integrations/github/check_runs.py
  • src/main.py
  • src/presentation/github_formatter.py
  • src/rules/acknowledgment.py
  • src/rules/conditions/base.py
  • src/rules/conditions/filesystem.py
  • src/rules/conditions/pull_request.py
  • src/rules/loaders/github_loader.py
  • src/rules/models.py
  • src/rules/registry.py
  • src/rules/utils.py
  • src/rules/utils/codeowners.py
  • src/rules/utils/contributors.py
  • src/rules/utils/validation.py
  • src/webhooks/handlers/deployment_review.py
  • src/webhooks/handlers/deployment_status.py
  • src/webhooks/handlers/issue_comment.py

- Migrate all remaining standard logging calls to structlog
- Convert f-string interpolations into kwargs for structured log payloads
- Remove unused variables and fix enum types in core models
- Ensure 100% test coverage stability post-refactor

Signed-off-by: Dimitris Kargatzis <dkargatzis@gmail.com>
@dkargatzis dkargatzis force-pushed the fix/code-quality-improvements branch from 4a6c671 to d03f5d3 Compare February 26, 2026 21:25
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: 19

Caution

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

⚠️ Outside diff range comments (6)
src/event_processors/rule_creation.py (1)

33-70: 🛠️ Refactor suggestion | 🟠 Major

Structured logging does not follow prescribed field conventions.

The logging should use consistent fields: operation, subject_ids, decision, latency_ms as per the coding guidelines. Current implementation has several issues:

  1. Line 63 - logger.info("api_calls_made_1") has no structured fields.
  2. Lines 66-68 - Conditional logs lack context fields.
  3. Decorative separators (lines 33, 35, 60, 70) add noise without semantic value.
  4. Field naming - processing_time should be latency_ms for consistency.
♻️ Suggested refactor for structured logging
-            logger.info("=" * 80)
-            logger.info("processing_rule_creation_command_for", repo_full_name=task.repo_full_name)
-            logger.info("=" * 80)
+            logger.info(
+                "rule_creation_processing_started",
+                operation="rule_creation",
+                subject_ids={"repo": task.repo_full_name},
+            )
-            # Summary
-            logger.info("=" * 80)
-            logger.info("rule_creation_processing_completed_in_ms", processing_time=processing_time)
-            logger.info("feasible", success=feasibility_result.success)
-            logger.info("api_calls_made_1")
-
-            if feasibility_result.success:
-                logger.info("rule_is_feasible_yaml_provided")
-            else:
-                logger.info("rule_is_not_feasible_feedback_provided")
-
-            logger.info("=" * 80)
+            logger.info(
+                "rule_creation_processing_completed",
+                operation="rule_creation",
+                subject_ids={"repo": task.repo_full_name},
+                decision="feasible" if feasibility_result.success else "not_feasible",
+                latency_ms=processing_time,
+                api_calls_made=1,
+            )

Based on learnings: "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/rule_creation.py` around lines 33 - 70, The logs in
rule_creation.py use ad-hoc messages and separators; update all logger.info
calls in the rule creation flow (around _extract_rule_description, after
feasibility_agent.execute and in _post_result_to_comment handling) to use
structured fields: operation="process_rule_creation",
subject_ids=[task.repo_full_name], decision=(feasibility_result.success ?
"feasible" : "not_feasible"), latency_ms=processing_time (rename processing_time
to latency_ms), and include api_calls_made as an integer field instead of
logger.info("api_calls_made_1"); remove decorative separator logs and replace
conditional messages (rule_is_feasible_yaml_provided /
rule_is_not_feasible_feedback_provided) with a single structured log that sets
decision and any extra metadata.
src/event_processors/check_run.py (1)

38-43: ⚠️ Potential issue | 🟠 Major

Complete structured logging migration in check-run processing block.

This block still emits separator/f-string style logs, so the migrated event at Line 39 is not enough for consistent structured observability.

Suggested change
-        logger.info("=" * 80)
-        logger.info("processing_check_run_event_for", repo_full_name=task.repo_full_name)
-        logger.info(f"   Name: {check_run.get('name')}")
-        logger.info(f"   Status: {check_run.get('status')}")
-        logger.info(f"   Conclusion: {check_run.get('conclusion')}")
-        logger.info("=" * 80)
+        logger.info(
+            "processing_check_run_event",
+            operation="check_run",
+            subject_ids=[task.repo_full_name],
+            decision="processing",
+            latency_ms=0,
+            check_run_name=check_run.get("name"),
+            status=check_run.get("status"),
+            conclusion=check_run.get("conclusion"),
+        )

As per coding guidelines "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/check_run.py` around lines 38 - 43, Replace the
separator/f-string logs in the check-run block with a single structured
logger.info call: remove the "=" lines and the f-strings and log via logger.info
with fields operation="processing_check_run_event", subject_ids including
task.repo_full_name and check_run.get("id"), name=check_run.get("name"),
status=check_run.get("status"), decision=check_run.get("conclusion"), and
latency_ms using task.latency_ms or None if not present; update the code around
the existing logger usage and references to logger, task, and check_run to emit
these structured fields.
src/agents/acknowledgment_agent/agent.py (1)

186-197: ⚠️ Potential issue | 🟠 Major

Confidence threshold policy is not enforced before returning success.

The function returns success=True regardless of structured_result.confidence. It should reject or route to human review when confidence is below 0.5.

As per coding guidelines: "Implement confidence policy: reject or route to human-in-the-loop when confidence < 0.5".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/agent.py` around lines 186 - 197, The current
return always sets success=True regardless of structured_result.confidence;
update the logic in the function that builds the AgentResult (the block
returning AgentResult with keys
is_valid/reasoning/acknowledgable_violations/require_fixes/confidence/recommendations)
to enforce the confidence policy: if structured_result.confidence < 0.5, set
success to False (or add a route_to_human/human_review flag in the returned
data) and change the message to indicate low confidence and human review
required, otherwise return success=True as before; ensure you reference
structured_result.confidence when deciding the outcome so low-confidence cases
are rejected/routed.
src/main.py (1)

64-65: ⚠️ Potential issue | 🟠 Major

Startup path still bypasses structlog.

Line 64 still uses logging.info(...) instead of logger.info(...). This bypasses the structured pipeline you introduced in this module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.py` around lines 64 - 65, The startup message is using the root
logging call logging.info(...) which bypasses the structlog pipeline; replace
that call with the module's structured logger (logger.info(...)) so the startup
message flows through your configured structlog pipeline and include any
contextual data if needed; verify that the variable logger (the structlog logger
created earlier in this module) is in scope and used instead of the root logging
module.
src/agents/acknowledgment_agent/test_agent.py (1)

78-117: ⚠️ Potential issue | 🔴 Critical

This test can pass even when the agent fails.

At Line 113 and Line 116, failure paths only log errors. The test never fails on unsuccessful execution or exceptions, which can mask regressions.

Proposed fix
     try:
         # Test evaluation
         result = await agent.evaluate_acknowledgment(
@@
-        if result.success:
+        assert result.success, f"Acknowledgment evaluation failed: {result.message}"
+        if result.success:
             logger.info("acknowledgment_evaluation_completed_successfully")
@@
-        else:
-            logger.error("acknowledgment_evaluation_failed", message=result.message)
+        else:
+            pytest.fail(f"Acknowledgment evaluation failed: {result.message}")

     except Exception as e:
-        logger.error("test_failed_with_error", e=e)
+        logger.error("test_failed_with_error", error=str(e))
+        raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/test_agent.py` around lines 78 - 117, The
test currently only logs errors when agent.evaluate_acknowledgment returns a
failing result or raises an exception; change it so failures cause the test to
fail: after calling agent.evaluate_acknowledgment, if result.success is False
raise an AssertionError (or call pytest.fail) including result.message and any
key diagnostic fields (e.g., result.data.get('reasoning')), and in the except
Exception as e block re-raise the exception (or call pytest.fail with str(e))
instead of just logging; update references to agent.evaluate_acknowledgment,
result, and the except block so the test will not pass silently on failures.
src/core/utils/logging.py (1)

52-69: ⚠️ Potential issue | 🟠 Major

Core logging utility still emits interpolated strings in critical paths.

log_operation (Line 52-69) and wrapper error logs (Line 104 and Line 122) still use interpolated messages instead of canonical event+kwargs fields. This leaves the migration incomplete in the shared logging layer and breaks consistency for downstream logs.

As per coding guidelines: "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

Also applies to: 103-106, 121-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/logging.py` around lines 52 - 69, The log_operation context
manager is emitting interpolated f-strings; change its logger.info and
logger.error calls to use structured logging fields instead of embedding the
operation in the message: emit a short constant message (e.g.,
"operation.started", "operation.completed", "operation.failed") and pass
operation, latency_ms and any error into extra (or structured kwargs) so fields
are present as operation, subject_ids, decision, latency_ms; do the same fix for
the wrapper error log sites in this module (the wrapper/error logging blocks
referenced in the file) ensuring exc_info is preserved on errors and that logger
calls use the structured fields rather than f-strings.
🧹 Nitpick comments (8)
src/agents/engine_agent/nodes.py (4)

57-57: Consider adding error_type to structured error logs.

The structured error log is an improvement, but per guidelines, domain errors should include error_type for better categorization in monitoring systems.

-        logger.error("error_in_rule_analysis", error=str(e))
+        logger.error("error_in_rule_analysis", error=str(e), error_type=type(e).__name__)

Same applies to lines 139, 208, and 276.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/nodes.py` at line 57, Add an error_type field to the
structured logger.error calls mentioned (the call at
logger.error("error_in_rule_analysis", error=str(e)) and the similar calls at
the other occurrences) so logs include a category for monitoring; update each
call in nodes.py (the logger.error invocations at the "error_in_rule_analysis"
site and the similar logger.error uses around lines referenced) to pass
error_type=str(type(e).__name__) alongside the existing message and error
details to preserve current behavior and add classification.

90-90: Event name lacks word separation.

The event name all_rules_mapped_to_validatorsconditions_skipping appears to have missing underscores, reducing readability in log queries.

-            logger.info("all_rules_mapped_to_validatorsconditions_skipping")
+            logger.info("all_rules_mapped_to_validator_conditions_skipping")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/nodes.py` at line 90, The log event string passed to
logger.info in the
logger.info("all_rules_mapped_to_validatorsconditions_skipping") call has
missing word separation; update that literal to a properly separated event name
(for example "all_rules_mapped_to_validators_conditions_skipping" or another
agreed-upon snake_case variant) wherever used, keeping the same logger.info call
and ensuring any other code or tests that match this event name are updated to
the new string.

53-54: Boundary logs missing required structured fields.

Per coding guidelines, boundary logs should include operation, subject_ids, decision, and latency_ms. Currently only latency_ms is provided. Consider adding the missing fields for full observability compliance.

         elapsed_ms = int((time.time() - start_time) * 1000)
-        logger.info("rule_analysis_completed", latency_ms=elapsed_ms)
+        logger.info(
+            "rule_analysis_completed",
+            operation="analyze_rule_descriptions",
+            subject_ids=[rd.rule_id for rd in state.rule_descriptions],
+            decision=f"found_{len(state.rule_descriptions)}_applicable_rules",
+            latency_ms=elapsed_ms,
+        )

The same pattern applies to lines 135-136, 204-205, and 272-273.

As per coding guidelines: "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/nodes.py` around lines 53 - 54, Update the boundary
structured logs to include the required fields by adding operation, subject_ids,
decision alongside latency_ms on the logger.info calls (e.g., the call that
currently logs "rule_analysis_completed" should become
logger.info("rule_analysis_completed", operation="rule_analysis",
subject_ids=[...], decision="...", latency_ms=elapsed_ms)); find and update the
other similar boundary log statements in this file that currently only emit
latency_ms (the other logger.info calls noted in the review) to follow the same
pattern so all boundary logs include operation, subject_ids, decision, and
latency_ms for full observability compliance.

37-48: Incomplete migration: f-string logs should use kwargs for structured payloads.

The PR objective states "Convert f-string interpolations into kwargs for structured log payloads," but these log calls still use f-strings. This prevents log aggregation tools from parsing and querying the structured fields.

Suggested refactor pattern
-        logger.info(f"🔍 Analyzing {len(state.rule_descriptions)} rule descriptions")
+        logger.info("analyzing_rule_descriptions", rule_count=len(state.rule_descriptions))

         # Filter rules applicable to this event type
         applicable_rules = []
         for rule_desc in state.rule_descriptions:
             if state.event_type in rule_desc.event_types:
                 applicable_rules.append(rule_desc)
-                logger.info(f"🔍 Rule '{rule_desc.description[:50]}...' is applicable to {state.event_type}")
+                logger.debug(
+                    "rule_applicable",
+                    rule_id=rule_desc.rule_id,
+                    event_type=state.event_type,
+                )
             else:
-                logger.info(
-                    f"🔍 Rule '{rule_desc.description[:50]}...' is NOT applicable (expects: {rule_desc.event_types})"
-                )
+                logger.debug(
+                    "rule_not_applicable",
+                    rule_id=rule_desc.rule_id,
+                    expected_event_types=rule_desc.event_types,
+                    actual_event_type=state.event_type,
+                )

Similar f-string patterns exist at lines 71, 84, 93, 126, 131, 155, 169-170, 182, 226, 251, 259-261, 312, 351, and 383.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/nodes.py` around lines 37 - 48, Logs in nodes.py are
still using f-strings (e.g., logger.info(f"🔍 Analyzing
{len(state.rule_descriptions)} rule descriptions") and the per-rule messages
that reference rule_desc.description and state.event_type); replace these
f-strings with structured-logging kwargs so fields are queryable (for example:
logger.info("Analyzing rule descriptions", count=len(state.rule_descriptions))
and for each rule use a single message with named fields like logger.info("Rule
applicability", rule_description=rule_desc.description[:50],
event_type=state.event_type, expected_event_types=rule_desc.event_types,
applicable=True/False) so you keep the same semantics but emit structured keys;
apply the same refactor to the other occurrences mentioned (lines noted in the
comment) ensuring unique field names (e.g., rule_description, event_type,
expected_event_types, applicable, count) to keep logs consistent.
src/event_processors/rule_creation.py (2)

74-82: Use standard exception logging pattern.

The exception is logged as e=e which is non-descriptive. Use exc_info=True to capture the full traceback or use a standard field name.

♻️ Suggested fix for exception logging
-            logger.error("error_processing_rule_creation", e=e)
+            logger.error(
+                "rule_creation_processing_failed",
+                operation="rule_creation",
+                subject_ids={"repo": task.repo_full_name},
+                exc_info=True,
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/rule_creation.py` around lines 74 - 82, The log call
inside the exception handler for rule creation currently passes the exception as
a non-descriptive field (logger.error("error_processing_rule_creation", e=e));
update this to use the standard exception logging pattern by calling
logger.error or logger.exception with exc_info=True (or use
logger.exception("error_processing_rule_creation") if you want stacktrace and
default error level) so the full traceback is captured; keep the surrounding
return of ProcessingResult (success=False, error=str(e), etc.) unchanged and
only replace the logger call in the exception block in rule_creation.py.

109-126: Add context to warning and error logs.

Line 110's warning provides no context for debugging. Lines 123 and 126 should include more structured fields for observability.

♻️ Suggested improvements
             if not issue_number or not task.installation_id:
-                logger.warning("no_issue_number_or_installationid_found")
+                logger.warning(
+                    "missing_required_identifiers",
+                    operation="post_feasibility_reply",
+                    has_issue_number=bool(issue_number),
+                    has_installation_id=bool(task.installation_id),
+                    subject_ids={"repo": task.repo_full_name},
+                )
                 return
             else:
-                logger.error("failed_to_post_feasibility_reply_to", issue_number=issue_number)
+                logger.error(
+                    "failed_to_post_feasibility_reply",
+                    operation="post_feasibility_reply",
+                    subject_ids={"repo": task.repo_full_name, "issue": issue_number},
+                )

         except Exception as e:
-            logger.error("error_posting_feasibility_reply", e=e)
+            logger.error(
+                "error_posting_feasibility_reply",
+                operation="post_feasibility_reply",
+                subject_ids={"repo": task.repo_full_name},
+                exc_info=True,
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/event_processors/rule_creation.py` around lines 109 - 126, The warning
and error logs lack contextual fields—update the logging around the feasibility
reply block (where _format_feasibility_reply is called and
github_client.create_issue_comment is invoked) to include relevant identifiers:
add issue_number, task.repo_full_name, task.installation_id (and task.id if
available) to the warning when issue_number or installation_id is missing; when
create_issue_comment returns, include those same structured fields in the
logger.info and logger.error calls; and in the exception handler, log the
exception alongside the structured identifiers (issue_number, repo_full_name,
installation_id) instead of passing the exception as a lone positional arg so
observability tools get both the error and context.
src/rules/conditions/pull_request.py (1)

73-74: Use exc_info=True or logger.exception() instead of passing exception as a field

At lines 73 and 97, logging the exception as e=e bypasses structlog's exception processing pipeline. Per structlog best practices, pass the exception via exc_info=True (or exc_info=e to pass the caught exception) and let processors render it consistently. Preferred approach: use logger.exception() inside the except block, which sets exc_info=True automatically.

# Before
logger.error("titlepatterncondition_invalid_regex_pattern", pattern=pattern, e=e)

# After (either)
logger.exception("titlepatterncondition_invalid_regex_pattern", pattern=pattern)
# or
logger.error("titlepatterncondition_invalid_regex_pattern", pattern=pattern, exc_info=True)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/conditions/pull_request.py` around lines 73 - 74, Replace the
logger.error calls that pass the caught exception as a field with
structlog-compatible exception logging: inside the exception handlers in
TitlePatternCondition (the block that logs
"titlepatterncondition_invalid_regex_pattern" when regex compilation or matching
fails), call logger.exception(...) or logger.error(..., exc_info=True) instead
of logger.error(..., e=e) so the exception is passed via exc_info and processed
by structlog correctly.
src/webhooks/handlers/issue_comment.py (1)

128-129: Normalize event keys for observability consistency.

Several event keys are truncated/concatenated (posted_feasibility_evaluation_result_as_a, comment_does_not_match_any_known, extractingacknowledgmentreason). Consistent snake_case keys improve searchability and dashboard filters.

Also applies to: 158-159, 169-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/webhooks/handlers/issue_comment.py` around lines 128 - 129, Several
logger event keys in issue_comment.py are concatenated/truncated; update the
logger.info calls to use consistent snake_case event keys (e.g., change
"extractingacknowledgmentreason" to "extracting_acknowledgment_reason" and
ensure similar snake_case for "posted_feasibility_evaluation_result_as_a" and
"comment_does_not_match_any_known"). Locate the logger.info invocations (the
ones returning WebhookResponse and the two other occurrences around the
mentioned lines) and replace the current string keys with clear snake_case
variants so observability/searchability is consistent across events.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/agents/acknowledgment_agent/agent.py`:
- Around line 89-91: Logging currently emits raw user input and internal
reasoning (e.g., acknowledgment_reason, full reasoning_text, and full
traceback_text). Replace direct logger.info calls that log
acknowledgment_reason, reasoning_text, and traceback_text with sanitized
summaries: redact PII/secrets, log a short hash or length and a redaction flag
(e.g., "reason_redacted", "reason_length", "traceback_summary") and/or a safe
metadata tuple (count of violations, categories) instead of full content;
implement or call an existing sanitize/redact helper before logging and ensure
logger.info("violations_to_evaluate", count=len(violations)) remains but that
any strings passed (acknowledgment_reason, reasoning_text, traceback_text) are
replaced by their sanitized summaries in the same function/agent (agent.py)
where these variables are constructed/used.

In `@src/agents/engine_agent/agent.py`:
- Around line 121-128: The completion log after running the hybrid graph (where
result = await self._execute_with_timeout(self.graph.ainvoke(initial_state)))
currently only logs latency_ms; update the final structured logger call (the
logger.info near "rule_engine_evaluation_completed") to include the required
fields operation (e.g., "rule_engine_evaluation_completed"), subject_ids
(derived from initial_state or result), decision (the computed decision from
result), and latency_ms; do the same for the other boundary log near lines
167-170 so both logger.info calls emit a single structured event containing
operation, subject_ids, decision, and latency_ms, using the existing symbols
logger, _execute_with_timeout, graph.ainvoke, initial_state, and result to
populate fields.

In `@src/core/utils/caching.py`:
- Line 178: The current logger.debug call logs the raw cache_key (derived from
function args) which can leak secrets/PII; update the logging in the cache hit
path (the logger.debug call that uses func.__name__ and cache_key) to never emit
the raw cache_key—instead compute and log a non-reversible fingerprint (e.g.,
SHA-256 or HMAC of cache_key) or a redacted marker, and replace cache_key in the
log with that fingerprint/redaction; ensure all similar calls (including the
other occurrence around the same block) are changed so no raw cache_key is
written to logs.
- Line 65: Structured cache log calls like logger.debug("cache_hit_for",
key=key) are missing required boundary fields; update all
logger.debug/logger.info calls in this module (the occurrences around the cache
hit/miss branches identified by key and the cache functions such as
cache_get/cache_set or similar) to include operation (e.g.,
"cache_get"/"cache_set"), subject_ids (use [key] or the relevant id list),
decision ("hit" or "miss"), and latency_ms (compute elapsed ms from the entry
point timing variable or capture start time around the cache operation). Ensure
each structured event includes these four fields alongside existing fields so
they conform to the logging contract; apply the same change to the other listed
logger lines (near lines 82, 89, 95, 106, 178, 182).

In `@src/core/utils/timeout.py`:
- Line 45: Replace the generic boundary log logger.error("event", msg=msg) with
a semantic, structured timeout event that includes the required fields: use a
clear event name (e.g., "timeout" or "operation_timeout") and pass operation,
subject_ids, decision, and latency_ms as explicit fields; locate the
logger.error call in timeout.py and ensure latency_ms is computed from the
existing duration/timing variables (convert to milliseconds) and include subject
identifiers and the decision value so the log becomes
logger.error("operation_timeout", operation=..., subject_ids=..., decision=...,
latency_ms=..., msg=msg) or the equivalent for your structured-logger API.

In `@src/event_processors/deployment_protection_rule.py`:
- Line 79: The decision log call logger.info("no_rules_found_for_repository")
(and the other similar logger.info calls in this module) must be changed to
structured logging with the boundary fields required by our guidelines: include
operation (e.g., "deployment_protection_rule_evaluation" or the existing
operation name used in this module), subject_ids (the repo or deployment
identifiers available in the function), decision (e.g.,
"no_rules_found"/"allow"/"deny"), and latency_ms (compute using the start time
used in this processor). Update the logger.info invocation(s) in
deployment_protection_rule.py (the lines emitting
"no_rules_found_for_repository" and the other two info logs) to pass a dict or
keyword fields with keys operation, subject_ids, decision, and latency_ms
instead of a plain message so they become structured boundary logs.

In `@src/event_processors/pull_request/processor.py`:
- Around line 67-71: Replace the separator and f-string prints with structured
start/finish log events: emit logger.info with fields operation (e.g.
"process_pr_event.start" and "process_pr_event.finish"), subject_ids containing
repo_full_name and pr_number, decision (None at start, actual decision at
finish), and latency_ms (compute by capturing a start_time in the PR processing
entry point and subtracting it at the end). Update the logger calls that
currently print "=" * 80, "processing_pr_event_for", f"   Action:
{task.payload.get('action')}", and "pr_number" (the block around logger.info in
the processing function) and mirror the same structured pattern for the finish
logs referenced around the other logger calls (lines ~182-183) so all boundary
logs use the required fields.

In `@src/event_processors/push.py`:
- Around line 36-40: The current logging in push event processing mixes
banner/f-string and unstructured messages (e.g., logger.info("=" * 80),
logger.info("processing_push_event_for", repo_full_name=task.repo_full_name),
logger.info("ref", ref=ref), logger.info(f"   Commits: {len(commits)}")) which
violates the structured logging contract; replace these with single structured
boundary logs that include the required fields operation (e.g.,
"push_event.processing.start"/".complete"/".skip"/".error"), subject_ids (e.g.,
task.repo_full_name and ref or commit ids), decision (e.g.,
"started"/"skipped"/"error"/"completed"), and latency_ms (compute for
completion/error paths), and apply the same change for the other logger.info
calls referenced (the other start/skip/error/completion sites in this file).
Locate usages of logger.info in the push processing function and consolidate
into event-style structured logs providing those four fields and any contextual
fields like commits count without using f-strings or banner separators.

In `@src/event_processors/rule_creation.py`:
- Line 49: The current logger.info call logs the full user-provided
rule_description which may contain PII/secrets; update the logging in
src/event_processors/rule_creation.py so that logger.info does not emit the raw
rule_description but instead logs only safe metadata or a sanitized/truncated
version (e.g., first N characters + "[TRUNCATED]" or a hash/summary) and/or a
redaction marker; change the call that uses logger.info("rule_description",
rule_description=rule_description) to produce and log a
sanitized_rule_description derived from rule_description and keep the original
text out of logs while preserving traceability (include function/variable names:
logger.info, rule_description, sanitized_rule_description).

In `@src/integrations/github/check_runs.py`:
- Around line 43-44: The boundary logs at the check-run early-returns (the
logger.warning/logger.info calls in this module) are missing required structured
fields; update each of the log calls (the ones that currently call
logger.warning("cannot_create_check_run_for_sha", repo=repo) and the similar
calls around lines ~62, ~89, ~119) to include operation (e.g.,
operation="create_check_run"), subject_ids (an array identifying the
repo/sha/installation id used by the flow), decision (a short string like
"failed" or "skipped" that reflects why we returned), and latency_ms (either
compute elapsed ms from the function entry timestamp if available or set to
None/0). Ensure you add those fields to the same logger call (not a separate
log) so the messages conform to the boundary schema and reference the existing
logger invocation sites (the logger.warning/logger.info calls in this file).
- Around line 65-66: The logger calls pass raw exception objects (e=e) which
JSONRenderer can't serialize; update both logger.error invocations to pass a
serializable error string and include exc_info for stack traces—replace the call
that logs "error_creating_check_run" (around the check run creation code) with
logger.error(..., error=str(e), exc_info=True) and likewise replace the
"error_creating_check_run_with_acknowledgment" call with logger.error(...,
error=str(e), exc_info=True); locate these by the log message keys to apply the
change.

In `@src/main.py`:
- Around line 88-89: The lifecycle boundary logs in src/main.py (the logger.info
call that currently reads "Event handlers registered, background workers
started, and deployment scheduler started." and the similar startup/shutdown
messages around lines 92-93 and 98-99) must be changed to emit the canonical
structured fields: include operation (e.g., "startup" or "shutdown"),
subject_ids (array or empty list identifying affected components), decision
(e.g., "succeeded" or "failed"), and latency_ms (numeric latency), by passing
those fields into your structured logger call (e.g., via logger.info with an
attributes/extra dict or the project’s logger-binding API) alongside the
existing message so each lifecycle boundary log has operation, subject_ids,
decision, and latency_ms populated.

In `@src/rules/conditions/pull_request.py`:
- Around line 60-61: The boundary logs (e.g., the logger.debug call with message
"titlepatterncondition_title_matches_pattern" and other decision/debug sites
inside the TitlePatternCondition class methods evaluate/validate) must include
the required structured fields: operation, subject_ids, decision, and
latency_ms; capture a start timestamp at the beginning of each evaluate/validate
invocation, compute latency_ms before each boundary log, set operation to a
clear string like "title_pattern_condition.evaluate" or
"title_pattern_condition.validate", populate subject_ids from the pull request
identifier(s) (e.g., pr.id or pr.number), and include the boolean decision value
(match/validate result) in the decision field—update all similar logger.debug
calls noted in the review (the messages around lines 60, 94-95, 143-147,
177-183, 220-226, 260-267, 357-358) to pass these extra fields.

In `@src/rules/loaders/github_loader.py`:
- Around line 42-43: Replace the interpolated logger.info calls around the
get_file_content call and the later log at lines ~70-71 with structured logging
using event keys and kwargs; for the first call that currently logs f"Fetching
rules for repository: {repository} (installation: {installation_id})", call
logger.info with operation="fetch_rules", subject_ids={"repository": repository,
"installation_id": installation_id}, and any additional fields (e.g.,
"rules_file_path") instead of an f-string, then do the same for the logger call
near the get_file_content result (around lines 70-71) — use a clear operation
value (e.g., "rules_lookup" or "rules_not_found"), include subject_ids with
repository and installation_id, and include latency_ms/decision fields as
appropriate to match the file's structured logging schema.

In `@src/rules/utils.py`:
- Line 24: The log call at logger.info("posted_validation_result_to_pr_in", ...)
must include the required boundary fields; update the logger.info invocation in
src/rules/utils.py (the "posted_validation_result_to_pr_in" event) to pass
operation="posted_validation_result_to_pr_in", subject_ids=[pr_number] (or a
list of relevant IDs), decision=<decision> (use the existing decision
variable/result), and latency_ms=<computed integer> (compute as milliseconds
from the request start timestamp, e.g. int((time.time() - start_ts)*1000) or use
the existing latency variable if present); ensure the decision and
start_ts/latency variables are available or computed before calling logger.info.

In `@src/rules/utils/codeowners.py`:
- Line 42: The structured log call logger.warning("invalid_codeowners_line",
line_num=line_num, line=line) is missing required boundary fields; update every
such call that emits the "invalid_codeowners_line" event (and similar warnings
in this module) to include operation (e.g., "parse_codeowners"), subject_ids (an
array identifying the resource, e.g., [repo_id or filename] or [] if
unavailable), decision (e.g., "failure" or "invalid"), and latency_ms (numeric,
0 if not measured) so the event conforms to the logging contract; modify the
logger.warning invocations in this file (the ones with message
"invalid_codeowners_line") to pass those four named fields alongside line_num
and line.

In `@src/rules/utils/contributors.py`:
- Around line 46-50: The logging calls in src/rules/utils/contributors.py must
be converted to structured boundary logs that include the mandatory fields
operation, subject_ids, decision, and latency_ms and remove any f-string
messages; update the early cache branch
(logger.debug("using_cached_past_contributors_for", repo=repo)) and the
subsequent fetch/info calls (logger.info("fetching_past_contributors_for",
repo=repo) and the interpolated message later in get_past_contributors) to use
logger.<level>(message, operation="get_past_contributors", subject_ids=[repo],
decision="cache_hit" or "cache_miss"/"allowed"/"denied" as appropriate,
latency_ms=elapsed_ms) or similar values, and replace the f-string log at the
later call with the same structured call so all boundary events in
get_past_contributors emit the required fields.

In `@src/webhooks/handlers/deployment_status.py`:
- Around line 31-33: The current code mixes structured logger calls and an
f-string (logger.info + f-string) in the deployment enqueue path; replace the
three separate logger.info calls with a single structured boundary log using
logger.info and the required fields (operation, subject_ids, decision,
latency_ms) plus the environment and repo identifier—e.g., build one logger.info
call that sets operation="enqueuing_deployment_status_event", subject_ids or
repo_full_name=repo_full_name, decision=state,
environment=deployment.get("environment","unknown") and include latency_ms when
available; update the code that currently calls
logger.info("enqueuing_deployment_status_event_for", ...), logger.info("state",
...), and logger.info(f"   Environment: {deployment.get('environment',
'unknown')}") to this single structured call so the boundary event is emitted
consistently.

In `@src/webhooks/handlers/issue_comment.py`:
- Around line 183-184: The code logs user-supplied `reason` verbatim via
`logger.info("pattern_matched_reason", reason=reason)` — change this to avoid
PII/secrets by removing the raw `reason` from logs and instead log redacted
metadata such as `reason_length` and a non-reversible checksum (e.g., sha256 or
truncated hash) and/or a short redaction flag; keep the defensive `if reason:`
check in the same place (in the same function where `logger` and `reason` are
used) but replace the logged payload so no raw `reason` text is emitted while
preserving ability to correlate events via the checksum/length metadata.

---

Outside diff comments:
In `@src/agents/acknowledgment_agent/agent.py`:
- Around line 186-197: The current return always sets success=True regardless of
structured_result.confidence; update the logic in the function that builds the
AgentResult (the block returning AgentResult with keys
is_valid/reasoning/acknowledgable_violations/require_fixes/confidence/recommendations)
to enforce the confidence policy: if structured_result.confidence < 0.5, set
success to False (or add a route_to_human/human_review flag in the returned
data) and change the message to indicate low confidence and human review
required, otherwise return success=True as before; ensure you reference
structured_result.confidence when deciding the outcome so low-confidence cases
are rejected/routed.

In `@src/agents/acknowledgment_agent/test_agent.py`:
- Around line 78-117: The test currently only logs errors when
agent.evaluate_acknowledgment returns a failing result or raises an exception;
change it so failures cause the test to fail: after calling
agent.evaluate_acknowledgment, if result.success is False raise an
AssertionError (or call pytest.fail) including result.message and any key
diagnostic fields (e.g., result.data.get('reasoning')), and in the except
Exception as e block re-raise the exception (or call pytest.fail with str(e))
instead of just logging; update references to agent.evaluate_acknowledgment,
result, and the except block so the test will not pass silently on failures.

In `@src/core/utils/logging.py`:
- Around line 52-69: The log_operation context manager is emitting interpolated
f-strings; change its logger.info and logger.error calls to use structured
logging fields instead of embedding the operation in the message: emit a short
constant message (e.g., "operation.started", "operation.completed",
"operation.failed") and pass operation, latency_ms and any error into extra (or
structured kwargs) so fields are present as operation, subject_ids, decision,
latency_ms; do the same fix for the wrapper error log sites in this module (the
wrapper/error logging blocks referenced in the file) ensuring exc_info is
preserved on errors and that logger calls use the structured fields rather than
f-strings.

In `@src/event_processors/check_run.py`:
- Around line 38-43: Replace the separator/f-string logs in the check-run block
with a single structured logger.info call: remove the "=" lines and the
f-strings and log via logger.info with fields
operation="processing_check_run_event", subject_ids including
task.repo_full_name and check_run.get("id"), name=check_run.get("name"),
status=check_run.get("status"), decision=check_run.get("conclusion"), and
latency_ms using task.latency_ms or None if not present; update the code around
the existing logger usage and references to logger, task, and check_run to emit
these structured fields.

In `@src/event_processors/rule_creation.py`:
- Around line 33-70: The logs in rule_creation.py use ad-hoc messages and
separators; update all logger.info calls in the rule creation flow (around
_extract_rule_description, after feasibility_agent.execute and in
_post_result_to_comment handling) to use structured fields:
operation="process_rule_creation", subject_ids=[task.repo_full_name],
decision=(feasibility_result.success ? "feasible" : "not_feasible"),
latency_ms=processing_time (rename processing_time to latency_ms), and include
api_calls_made as an integer field instead of logger.info("api_calls_made_1");
remove decorative separator logs and replace conditional messages
(rule_is_feasible_yaml_provided / rule_is_not_feasible_feedback_provided) with a
single structured log that sets decision and any extra metadata.

In `@src/main.py`:
- Around line 64-65: The startup message is using the root logging call
logging.info(...) which bypasses the structlog pipeline; replace that call with
the module's structured logger (logger.info(...)) so the startup message flows
through your configured structlog pipeline and include any contextual data if
needed; verify that the variable logger (the structlog logger created earlier in
this module) is in scope and used instead of the root logging module.

---

Nitpick comments:
In `@src/agents/engine_agent/nodes.py`:
- Line 57: Add an error_type field to the structured logger.error calls
mentioned (the call at logger.error("error_in_rule_analysis", error=str(e)) and
the similar calls at the other occurrences) so logs include a category for
monitoring; update each call in nodes.py (the logger.error invocations at the
"error_in_rule_analysis" site and the similar logger.error uses around lines
referenced) to pass error_type=str(type(e).__name__) alongside the existing
message and error details to preserve current behavior and add classification.
- Line 90: The log event string passed to logger.info in the
logger.info("all_rules_mapped_to_validatorsconditions_skipping") call has
missing word separation; update that literal to a properly separated event name
(for example "all_rules_mapped_to_validators_conditions_skipping" or another
agreed-upon snake_case variant) wherever used, keeping the same logger.info call
and ensuring any other code or tests that match this event name are updated to
the new string.
- Around line 53-54: Update the boundary structured logs to include the required
fields by adding operation, subject_ids, decision alongside latency_ms on the
logger.info calls (e.g., the call that currently logs "rule_analysis_completed"
should become logger.info("rule_analysis_completed", operation="rule_analysis",
subject_ids=[...], decision="...", latency_ms=elapsed_ms)); find and update the
other similar boundary log statements in this file that currently only emit
latency_ms (the other logger.info calls noted in the review) to follow the same
pattern so all boundary logs include operation, subject_ids, decision, and
latency_ms for full observability compliance.
- Around line 37-48: Logs in nodes.py are still using f-strings (e.g.,
logger.info(f"🔍 Analyzing {len(state.rule_descriptions)} rule descriptions")
and the per-rule messages that reference rule_desc.description and
state.event_type); replace these f-strings with structured-logging kwargs so
fields are queryable (for example: logger.info("Analyzing rule descriptions",
count=len(state.rule_descriptions)) and for each rule use a single message with
named fields like logger.info("Rule applicability",
rule_description=rule_desc.description[:50], event_type=state.event_type,
expected_event_types=rule_desc.event_types, applicable=True/False) so you keep
the same semantics but emit structured keys; apply the same refactor to the
other occurrences mentioned (lines noted in the comment) ensuring unique field
names (e.g., rule_description, event_type, expected_event_types, applicable,
count) to keep logs consistent.

In `@src/event_processors/rule_creation.py`:
- Around line 74-82: The log call inside the exception handler for rule creation
currently passes the exception as a non-descriptive field
(logger.error("error_processing_rule_creation", e=e)); update this to use the
standard exception logging pattern by calling logger.error or logger.exception
with exc_info=True (or use logger.exception("error_processing_rule_creation") if
you want stacktrace and default error level) so the full traceback is captured;
keep the surrounding return of ProcessingResult (success=False, error=str(e),
etc.) unchanged and only replace the logger call in the exception block in
rule_creation.py.
- Around line 109-126: The warning and error logs lack contextual fields—update
the logging around the feasibility reply block (where _format_feasibility_reply
is called and github_client.create_issue_comment is invoked) to include relevant
identifiers: add issue_number, task.repo_full_name, task.installation_id (and
task.id if available) to the warning when issue_number or installation_id is
missing; when create_issue_comment returns, include those same structured fields
in the logger.info and logger.error calls; and in the exception handler, log the
exception alongside the structured identifiers (issue_number, repo_full_name,
installation_id) instead of passing the exception as a lone positional arg so
observability tools get both the error and context.

In `@src/rules/conditions/pull_request.py`:
- Around line 73-74: Replace the logger.error calls that pass the caught
exception as a field with structlog-compatible exception logging: inside the
exception handlers in TitlePatternCondition (the block that logs
"titlepatterncondition_invalid_regex_pattern" when regex compilation or matching
fails), call logger.exception(...) or logger.error(..., exc_info=True) instead
of logger.error(..., e=e) so the exception is passed via exc_info and processed
by structlog correctly.

In `@src/webhooks/handlers/issue_comment.py`:
- Around line 128-129: Several logger event keys in issue_comment.py are
concatenated/truncated; update the logger.info calls to use consistent
snake_case event keys (e.g., change "extractingacknowledgmentreason" to
"extracting_acknowledgment_reason" and ensure similar snake_case for
"posted_feasibility_evaluation_result_as_a" and
"comment_does_not_match_any_known"). Locate the logger.info invocations (the
ones returning WebhookResponse and the two other occurrences around the
mentioned lines) and replace the current string keys with clear snake_case
variants so observability/searchability is consistent across events.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a6c671 and d03f5d3.

📒 Files selected for processing (44)
  • src/agents/acknowledgment_agent/agent.py
  • src/agents/acknowledgment_agent/test_agent.py
  • src/agents/base.py
  • src/agents/engine_agent/agent.py
  • src/agents/engine_agent/models.py
  • src/agents/engine_agent/nodes.py
  • src/agents/factory.py
  • src/agents/feasibility_agent/agent.py
  • src/agents/feasibility_agent/nodes.py
  • src/api/dependencies.py
  • src/core/models.py
  • src/core/utils/caching.py
  • src/core/utils/logging.py
  • src/core/utils/metrics.py
  • src/core/utils/retry.py
  • src/core/utils/timeout.py
  • src/event_processors/base.py
  • src/event_processors/check_run.py
  • src/event_processors/deployment.py
  • src/event_processors/deployment_protection_rule.py
  • src/event_processors/deployment_review.py
  • src/event_processors/deployment_status.py
  • src/event_processors/pull_request/enricher.py
  • src/event_processors/pull_request/processor.py
  • src/event_processors/push.py
  • src/event_processors/rule_creation.py
  • src/event_processors/violation_acknowledgment.py
  • src/integrations/github/check_runs.py
  • src/main.py
  • src/presentation/github_formatter.py
  • src/rules/acknowledgment.py
  • src/rules/conditions/base.py
  • src/rules/conditions/filesystem.py
  • src/rules/conditions/pull_request.py
  • src/rules/loaders/github_loader.py
  • src/rules/models.py
  • src/rules/registry.py
  • src/rules/utils.py
  • src/rules/utils/codeowners.py
  • src/rules/utils/contributors.py
  • src/rules/utils/validation.py
  • src/webhooks/handlers/deployment_review.py
  • src/webhooks/handlers/deployment_status.py
  • src/webhooks/handlers/issue_comment.py
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/presentation/github_formatter.py
  • src/event_processors/deployment.py
  • src/agents/feasibility_agent/agent.py
  • src/agents/base.py
  • src/rules/acknowledgment.py
  • src/event_processors/deployment_status.py
  • src/event_processors/deployment_review.py
  • src/rules/registry.py
  • src/agents/engine_agent/models.py
  • src/core/utils/metrics.py
  • src/agents/factory.py
  • src/event_processors/violation_acknowledgment.py
  • src/rules/conditions/base.py
  • src/agents/feasibility_agent/nodes.py
  • src/rules/models.py
  • src/rules/utils/validation.py
  • src/event_processors/pull_request/enricher.py
  • src/core/utils/retry.py
  • src/rules/conditions/filesystem.py
  • src/event_processors/base.py
  • src/webhooks/handlers/deployment_review.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

**/*.py: Use modern typing only: dict[str, Any], list[str], str | None (no Dict, List, Optional)
GitHub/HTTP/DB calls must be async def; avoid blocking calls (time.sleep, sync HTTP) in async paths
All agent outputs and external payloads must use validated BaseModel from Pydantic
Use dataclasses for internal immutable state where appropriate
Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms
Implement Agent pattern: single-responsibility agents with typed inputs/outputs
Use Decorator pattern for retries, metrics, caching as cross-cutting concerns
Agent outputs must include: decision, confidence (0..1), short reasoning, recommendations, strategy_used
Implement confidence policy: reject or route to human-in-the-loop when confidence < 0.5
Use minimal, step-driven prompts; provide Chain-of-Thought only for complexity > 0.7 or ambiguity > 0.6
Strip secrets/PII from agent prompts; scope tools; keep raw reasoning out of logs (store summaries only)
Cache idempotent lookups; lazy-import heavy dependencies; bound fan-out with asyncio.Semaphore
Avoid redundant LLM calls; memoize per event when safe
Use domain errors (e.g., AgentError) with error_type, message, context, timestamp, retry_count
Use exponential backoff for transient failures; circuit-break noisy integrations when needed
Fail closed for risky decisions; provide actionable remediation in error paths
Validate all external inputs; verify webhook signatures
Implement prompt-injection hardening; sanitize repository content passed to LLMs
Performance targets: Static validation ~<100ms typical, hybrid decisions sub-second when cache warm, budget LLM paths thoughtfully
Reject old typing syntax (Dict, List, Optional) in code review
Reject blocking calls in async code; reject bare except: clauses; reject swallowed errors
Reject LLM calls for trivial/deterministic checks
Reject unvalidated agent outputs and missing confidenc...

Files:

  • src/webhooks/handlers/deployment_status.py
  • src/agents/acknowledgment_agent/agent.py
  • src/core/utils/timeout.py
  • src/event_processors/pull_request/processor.py
  • src/rules/conditions/pull_request.py
  • src/rules/utils/codeowners.py
  • src/event_processors/check_run.py
  • src/api/dependencies.py
  • src/agents/acknowledgment_agent/test_agent.py
  • src/main.py
  • src/event_processors/push.py
  • src/agents/engine_agent/nodes.py
  • src/event_processors/rule_creation.py
  • src/rules/utils/contributors.py
  • src/rules/loaders/github_loader.py
  • src/core/models.py
  • src/webhooks/handlers/issue_comment.py
  • src/agents/engine_agent/agent.py
  • src/event_processors/deployment_protection_rule.py
  • src/integrations/github/check_runs.py
  • src/core/utils/caching.py
  • src/rules/utils.py
  • src/core/utils/logging.py
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: warestack/watchflow PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2026-01-31T19:35:22.504Z
Learning: Applies to **/*.py : Use structured logging at boundaries with fields: `operation`, `subject_ids`, `decision`, `latency_ms`
📚 Learning: 2026-01-31T19:35:22.504Z
Learnt from: CR
Repo: warestack/watchflow PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2026-01-31T19:35:22.504Z
Learning: Applies to **/*.py : Strip secrets/PII from agent prompts; scope tools; keep raw reasoning out of logs (store summaries only)

Applied to files:

  • src/agents/acknowledgment_agent/agent.py
  • src/agents/acknowledgment_agent/test_agent.py
  • src/agents/engine_agent/agent.py
📚 Learning: 2026-01-31T19:35:22.504Z
Learnt from: CR
Repo: warestack/watchflow PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2026-01-31T19:35:22.504Z
Learning: Applies to **/*.py : Use structured logging at boundaries with fields: `operation`, `subject_ids`, `decision`, `latency_ms`

Applied to files:

  • src/agents/acknowledgment_agent/agent.py
  • src/rules/conditions/pull_request.py
  • src/rules/utils/codeowners.py
  • src/agents/engine_agent/nodes.py
  • src/agents/engine_agent/agent.py
  • src/core/utils/logging.py
📚 Learning: 2026-01-31T19:35:22.504Z
Learnt from: CR
Repo: warestack/watchflow PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2026-01-31T19:35:22.504Z
Learning: Applies to **/*.py : Reject blocking calls in async code; reject bare `except:` clauses; reject swallowed errors

Applied to files:

  • src/api/dependencies.py
📚 Learning: 2026-01-31T19:35:22.504Z
Learnt from: CR
Repo: warestack/watchflow PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2026-01-31T19:35:22.504Z
Learning: Applies to tests/**/*.py : Write integration tests for webhook parsing, idempotency, multi-agent coordination, and state persistence

Applied to files:

  • src/agents/acknowledgment_agent/test_agent.py
🧬 Code graph analysis (12)
src/agents/acknowledgment_agent/agent.py (1)
src/agents/base.py (1)
  • AgentResult (18-24)
src/event_processors/pull_request/processor.py (3)
src/core/models.py (1)
  • repo_full_name (137-139)
src/tasks/task_queue.py (1)
  • repo_full_name (33-38)
src/core/utils/caching.py (1)
  • get (44-66)
src/event_processors/check_run.py (4)
src/agents/factory.py (1)
  • get_agent (21-53)
src/event_processors/base.py (2)
  • BaseEventProcessor (26-61)
  • ProcessingResult (16-23)
src/tasks/task_queue.py (2)
  • Task (19-47)
  • repo_full_name (33-38)
src/core/models.py (1)
  • repo_full_name (137-139)
src/api/dependencies.py (1)
src/core/models.py (1)
  • User (45-56)
src/main.py (2)
src/webhooks/dispatcher.py (1)
  • register_handler (25-28)
src/core/models.py (1)
  • EventType (91-105)
src/event_processors/push.py (1)
src/tasks/task_queue.py (2)
  • repo_full_name (33-38)
  • installation_id (41-47)
src/agents/engine_agent/nodes.py (1)
src/agents/engine_agent/models.py (1)
  • ValidationStrategy (27-32)
src/event_processors/rule_creation.py (5)
src/agents/factory.py (1)
  • get_agent (21-53)
src/agents/base.py (1)
  • AgentResult (18-24)
src/event_processors/base.py (2)
  • BaseEventProcessor (26-61)
  • ProcessingResult (16-23)
src/tasks/task_queue.py (2)
  • Task (19-47)
  • repo_full_name (33-38)
src/core/models.py (1)
  • repo_full_name (137-139)
src/rules/utils/contributors.py (1)
src/core/utils/caching.py (1)
  • AsyncCache (19-115)
src/webhooks/handlers/issue_comment.py (2)
src/core/models.py (3)
  • EventType (91-105)
  • WebhookEvent (116-144)
  • WebhookResponse (108-113)
src/webhooks/handlers/base.py (1)
  • EventHandler (6-25)
src/agents/engine_agent/agent.py (2)
src/agents/base.py (1)
  • _execute_with_timeout (94-112)
src/integrations/providers/vertex_ai_provider.py (1)
  • ainvoke (109-131)
src/integrations/github/check_runs.py (1)
src/core/models.py (1)
  • Violation (20-31)

Comment on lines +89 to +91
logger.info("evaluating_acknowledgment_request_from", commenter=commenter)
logger.info("acknowledgment_reason", reason=acknowledgment_reason)
logger.info("violations_to_evaluate", count=len(violations))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Raw user input and raw reasoning are being logged.

Line 90 logs acknowledgment_reason, Line 181 logs full reasoning text, and Line 203 logs full traceback text. These can expose secrets/PII or internal reasoning content; log sanitized summaries/metadata instead.

Based on learnings: "Strip secrets/PII from agent prompts; scope tools; keep raw reasoning out of logs (store summaries only)".

Also applies to: 181-183, 203-203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/acknowledgment_agent/agent.py` around lines 89 - 91, Logging
currently emits raw user input and internal reasoning (e.g.,
acknowledgment_reason, full reasoning_text, and full traceback_text). Replace
direct logger.info calls that log acknowledgment_reason, reasoning_text, and
traceback_text with sanitized summaries: redact PII/secrets, log a short hash or
length and a redaction flag (e.g., "reason_redacted", "reason_length",
"traceback_summary") and/or a safe metadata tuple (count of violations,
categories) instead of full content; implement or call an existing
sanitize/redact helper before logging and ensure
logger.info("violations_to_evaluate", count=len(violations)) remains but that
any strings passed (acknowledgment_reason, reasoning_text, traceback_text) are
replaced by their sanitized summaries in the same function/agent (agent.py)
where these variables are constructed/used.

Comment on lines +121 to 128
logger.info("rule_engine_initial_state_prepared")

# Run the hybrid graph with timeout
result = await self._execute_with_timeout(self.graph.ainvoke(initial_state), timeout=self.timeout)

execution_time = time.time() - start_time
logger.info(f"🔧 Rule Engine evaluation completed in {execution_time:.2f}s")
logger.info("rule_engine_evaluation_completed", latency_ms=int(execution_time * 1000))

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Evaluation boundary logging is missing required decision context.

Line 127 logs latency_ms, but completion context still lacks a canonical operation, subject_ids, and decision payload in the same boundary event. Please emit one final structured completion event with all required fields.

As per coding guidelines: "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

Also applies to: 167-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/engine_agent/agent.py` around lines 121 - 128, The completion log
after running the hybrid graph (where result = await
self._execute_with_timeout(self.graph.ainvoke(initial_state))) currently only
logs latency_ms; update the final structured logger call (the logger.info near
"rule_engine_evaluation_completed") to include the required fields operation
(e.g., "rule_engine_evaluation_completed"), subject_ids (derived from
initial_state or result), decision (the computed decision from result), and
latency_ms; do the same for the other boundary log near lines 167-170 so both
logger.info calls emit a single structured event containing operation,
subject_ids, decision, and latency_ms, using the existing symbols logger,
_execute_with_timeout, graph.ainvoke, initial_state, and result to populate
fields.

return None

logger.debug(f"Cache hit for '{key}'")
logger.debug("cache_hit_for", key=key)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the required boundary logging fields to structured events.

These events are structured, but they still miss the required fields (operation, subject_ids, decision, latency_ms), so they don’t conform to the repository logging contract.

Suggested schema-aligned logging shape
-logger.debug("cache_hit_for", key=key)
+logger.debug(
+    "cache_lookup",
+    operation="cache_lookup",
+    subject_ids=[key],
+    decision="hit",
+    latency_ms=0,
+)

-logger.debug("invalidated_cache_entry", key=key)
+logger.debug(
+    "cache_invalidate",
+    operation="cache_invalidate",
+    subject_ids=[key],
+    decision="invalidated",
+    latency_ms=0,
+)

-logger.debug("cache_miss_for_with_key", __name__=func.__name__, cache_key=cache_key)
+logger.debug(
+    "cache_lookup",
+    operation="cache_lookup",
+    subject_ids=[func.__name__],
+    decision="miss",
+    latency_ms=0,
+)
As per coding guidelines: `Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms`.

Also applies to: 82-82, 89-89, 95-95, 106-106, 178-178, 182-182

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/caching.py` at line 65, Structured cache log calls like
logger.debug("cache_hit_for", key=key) are missing required boundary fields;
update all logger.debug/logger.info calls in this module (the occurrences around
the cache hit/miss branches identified by key and the cache functions such as
cache_get/cache_set or similar) to include operation (e.g.,
"cache_get"/"cache_set"), subject_ids (use [key] or the relevant id list),
decision ("hit" or "miss"), and latency_ms (compute elapsed ms from the entry
point timing variable or capture start time around the cache operation). Ensure
each structured event includes these four fields alongside existing fields so
they conform to the logging contract; apply the same change to the other listed
logger lines (near lines 82, 89, 95, 106, 178, 182).


if cached_value is not None:
logger.debug(f"Cache hit for {func.__name__} with key '{cache_key}'")
logger.debug("cache_hit_for_with_key", __name__=func.__name__, cache_key=cache_key)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not log raw cache keys derived from function arguments.

cache_key may embed sensitive argument/kwarg values; logging it directly can leak secrets/PII.

Suggested redaction/fingerprinting approach
+import hashlib
...
 cache_key = key_func(*args, **kwargs) if key_func else f"{func.__name__}:{args}:{kwargs}"
+cache_key_hash = hashlib.sha256(cache_key.encode("utf-8")).hexdigest()[:12]

-logger.debug("cache_hit_for_with_key", __name__=func.__name__, cache_key=cache_key)
+logger.debug("cache_hit_for_with_key", function_name=func.__name__, cache_key_hash=cache_key_hash)

-logger.debug("cache_miss_for_with_key", __name__=func.__name__, cache_key=cache_key)
+logger.debug("cache_miss_for_with_key", function_name=func.__name__, cache_key_hash=cache_key_hash)
As per coding guidelines: `Reject secrets in logs, prompts, or test fixtures`.

Also applies to: 182-182

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/caching.py` at line 178, The current logger.debug call logs
the raw cache_key (derived from function args) which can leak secrets/PII;
update the logging in the cache hit path (the logger.debug call that uses
func.__name__ and cache_key) to never emit the raw cache_key—instead compute and
log a non-reversible fingerprint (e.g., SHA-256 or HMAC of cache_key) or a
redacted marker, and replace cache_key in the log with that
fingerprint/redaction; ensure all similar calls (including the other occurrence
around the same block) are changed so no raw cache_key is written to logs.

except TimeoutError as err:
msg = timeout_message or f"Operation timed out after {timeout} seconds"
logger.error(f"❌ {msg}")
logger.error("event", msg=msg)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a semantic timeout event with boundary fields.

Line 45 logs "event" with only msg, which loses actionable context for timeout triage. Include operation, subject_ids, decision, and latency_ms in this boundary log.

Suggested change
-        logger.error("event", msg=msg)
+        logger.error(
+            "operation_timeout",
+            operation="execute_with_timeout",
+            subject_ids=[],
+            decision="timeout",
+            latency_ms=int(timeout * 1000),
+            message=msg,
+        )

Based on learnings "Applies to **/*.py : Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/utils/timeout.py` at line 45, Replace the generic boundary log
logger.error("event", msg=msg) with a semantic, structured timeout event that
includes the required fields: use a clear event name (e.g., "timeout" or
"operation_timeout") and pass operation, subject_ids, decision, and latency_ms
as explicit fields; locate the logger.error call in timeout.py and ensure
latency_ms is computed from the existing duration/timing variables (convert to
milliseconds) and include subject identifiers and the decision value so the log
becomes logger.error("operation_timeout", operation=..., subject_ids=...,
decision=..., latency_ms=..., msg=msg) or the equivalent for your
structured-logger API.

installation_id=installation_id,
)
logger.info(f"Posted validation result to PR #{pr_number} in {repo_full_name}")
logger.info("posted_validation_result_to_pr_in", pr_number=pr_number, repo_full_name=repo_full_name)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add required boundary logging fields to validation-post event.

Line 24 should include operation, subject_ids, decision, and latency_ms to stay consistent with boundary observability standards.

As per coding guidelines "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils.py` at line 24, The log call at
logger.info("posted_validation_result_to_pr_in", ...) must include the required
boundary fields; update the logger.info invocation in src/rules/utils.py (the
"posted_validation_result_to_pr_in" event) to pass
operation="posted_validation_result_to_pr_in", subject_ids=[pr_number] (or a
list of relevant IDs), decision=<decision> (use the existing decision
variable/result), and latency_ms=<computed integer> (compute as milliseconds
from the request start timestamp, e.g. int((time.time() - start_ts)*1000) or use
the existing latency variable if present); ensure the decision and
start_ts/latency variables are available or computed before calling logger.info.

parts = line.split()
if len(parts) < 2:
logger.warning(f"Invalid CODEOWNERS line {line_num}: {line}")
logger.warning("invalid_codeowners_line", line_num=line_num, line=line)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add required boundary log fields to structured events.

On Line 193 and Line 202 (and for consistency, Line 42 and Line 102), the events omit required fields: operation, subject_ids, decision, latency_ms. This breaks the logging contract and makes cross-service querying/alerting harder.

Suggested patch
 import re
+import time
 from pathlib import Path
@@
-                logger.warning("invalid_codeowners_line", line_num=line_num, line=line)
+                logger.warning(
+                    "invalid_codeowners_line",
+                    operation="parse_codeowners",
+                    subject_ids=[f"line:{line_num}"],
+                    decision="skip_invalid_line",
+                    latency_ms=0,
+                    line_num=line_num,
+                    line=line,
+                )
@@
-            logger.error("invalid_regex_pattern", regex_pattern=regex_pattern)
+            logger.error(
+                "invalid_regex_pattern",
+                operation="match_codeowners_pattern",
+                subject_ids=[file_path, pattern],
+                decision="ignore_pattern",
+                latency_ms=0,
+                regex_pattern=regex_pattern,
+            )
             return False
@@
 def load_codeowners(repo_path: str = ".") -> CodeOwnersParser | None:
@@
+    started = time.perf_counter()
     codeowners_path = Path(repo_path) / "CODEOWNERS"
@@
-        logger.warning("codeowners_file_not_found_at", codeowners_path=codeowners_path)
+        logger.warning(
+            "codeowners_file_not_found_at",
+            operation="load_codeowners",
+            subject_ids=[str(codeowners_path)],
+            decision="not_found",
+            latency_ms=int((time.perf_counter() - started) * 1000),
+            codeowners_path=str(codeowners_path),
+        )
         return None
@@
-        logger.error("error_loading_codeowners_file", e=e)
+        logger.error(
+            "error_loading_codeowners_file",
+            operation="load_codeowners",
+            subject_ids=[str(codeowners_path)],
+            decision="error",
+            latency_ms=int((time.perf_counter() - started) * 1000),
+            error=str(e),
+        )
         return None

As per coding guidelines: Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms.

Also applies to: 102-102, 193-193, 202-202

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils/codeowners.py` at line 42, The structured log call
logger.warning("invalid_codeowners_line", line_num=line_num, line=line) is
missing required boundary fields; update every such call that emits the
"invalid_codeowners_line" event (and similar warnings in this module) to include
operation (e.g., "parse_codeowners"), subject_ids (an array identifying the
resource, e.g., [repo_id or filename] or [] if unavailable), decision (e.g.,
"failure" or "invalid"), and latency_ms (numeric, 0 if not measured) so the
event conforms to the logging contract; modify the logger.warning invocations in
this file (the ones with message "invalid_codeowners_line") to pass those four
named fields alongside line_num and line.

Comment on lines +46 to +50
logger.debug("using_cached_past_contributors_for", repo=repo)
return set(cached_value)

try:
logger.info(f"Fetching past contributors for {repo}")
logger.info("fetching_past_contributors_for", repo=repo)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add mandatory boundary logging fields and remove the remaining f-string log.

These events still miss the required schema (operation, subject_ids, decision, latency_ms), and Line 72 is still an interpolated message instead of structured payload. This makes logs harder to query/aggregate and violates the project logging contract.

Suggested pattern (example for get_past_contributors)
 async def get_past_contributors(
     self, repo: str, installation_id: int, min_contributions: int = 5, days_back: int = 365
 ) -> set[str]:
+    started_at = datetime.now()
     cache_key = f"{repo}_{min_contributions}_{days_back}"

     cached_value = self._contributors_cache.get(cache_key)
     if cached_value is not None:
-        logger.debug("using_cached_past_contributors_for", repo=repo)
+        logger.debug(
+            "using_cached_past_contributors",
+            operation="get_past_contributors",
+            subject_ids={"repo": repo},
+            decision="cache_hit",
+            latency_ms=int((datetime.now() - started_at).total_seconds() * 1000),
+        )
         return set(cached_value)

     try:
-        logger.info("fetching_past_contributors_for", repo=repo)
+        logger.info(
+            "fetching_past_contributors",
+            operation="get_past_contributors",
+            subject_ids={"repo": repo},
+            decision="start",
+            latency_ms=0,
+        )
         ...
-        logger.info(f"Found {len(past_contributors)} past contributors for {repo}")
+        logger.info(
+            "found_past_contributors",
+            operation="get_past_contributors",
+            subject_ids={"repo": repo},
+            decision="success",
+            latency_ms=int((datetime.now() - started_at).total_seconds() * 1000),
+            contributor_count=len(past_contributors),
+        )
         return past_contributors
     except Exception as e:
-        logger.error("error_fetching_past_contributors_for", repo=repo, e=e)
+        logger.error(
+            "error_fetching_past_contributors",
+            operation="get_past_contributors",
+            subject_ids={"repo": repo},
+            decision="error",
+            latency_ms=int((datetime.now() - started_at).total_seconds() * 1000),
+            error=str(e),
+        )
         return set()

As per coding guidelines "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".
Based on learnings "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

Also applies to: 72-72, 76-76, 102-102, 106-106, 168-168, 188-188, 217-217, 232-232, 247-247, 261-261

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/utils/contributors.py` around lines 46 - 50, The logging calls in
src/rules/utils/contributors.py must be converted to structured boundary logs
that include the mandatory fields operation, subject_ids, decision, and
latency_ms and remove any f-string messages; update the early cache branch
(logger.debug("using_cached_past_contributors_for", repo=repo)) and the
subsequent fetch/info calls (logger.info("fetching_past_contributors_for",
repo=repo) and the interpolated message later in get_past_contributors) to use
logger.<level>(message, operation="get_past_contributors", subject_ids=[repo],
decision="cache_hit" or "cache_miss"/"allowed"/"denied" as appropriate,
latency_ms=elapsed_ms) or similar values, and replace the f-string log at the
later call with the same structured call so all boundary events in
get_past_contributors emit the required fields.

Comment on lines +31 to 33
logger.info("enqueuing_deployment_status_event_for", repo_full_name=repo_full_name)
logger.info("state", state=state)
logger.info(f" Environment: {deployment.get('environment', 'unknown')}")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid mixed structured + f-string logs in deployment status enqueue path.

Line 31-Line 33 should emit one structured boundary event instead of mixed event/field + formatted string logs.

As per coding guidelines "Use structured logging at boundaries with fields: operation, subject_ids, decision, latency_ms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/webhooks/handlers/deployment_status.py` around lines 31 - 33, The current
code mixes structured logger calls and an f-string (logger.info + f-string) in
the deployment enqueue path; replace the three separate logger.info calls with a
single structured boundary log using logger.info and the required fields
(operation, subject_ids, decision, latency_ms) plus the environment and repo
identifier—e.g., build one logger.info call that sets
operation="enqueuing_deployment_status_event", subject_ids or
repo_full_name=repo_full_name, decision=state,
environment=deployment.get("environment","unknown") and include latency_ms when
available; update the code that currently calls
logger.info("enqueuing_deployment_status_event_for", ...), logger.info("state",
...), and logger.info(f"   Environment: {deployment.get('environment',
'unknown')}") to this single structured call so the boundary event is emitted
consistently.

Comment on lines +183 to 184
logger.info("pattern_matched_reason", reason=reason)
if reason: # Defensive: skip empty reasons—user typo, bot spam.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not log acknowledgment reasons verbatim.

At Line 183, reason is user-supplied text and can contain secrets/PII. Log redacted metadata (e.g., reason_length, checksum) instead of full content.

Based on learnings: "Strip secrets/PII from agent prompts; scope tools; keep raw reasoning out of logs (store summaries only)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/webhooks/handlers/issue_comment.py` around lines 183 - 184, The code logs
user-supplied `reason` verbatim via `logger.info("pattern_matched_reason",
reason=reason)` — change this to avoid PII/secrets by removing the raw `reason`
from logs and instead log redacted metadata such as `reason_length` and a
non-reversible checksum (e.g., sha256 or truncated hash) and/or a short
redaction flag; keep the defensive `if reason:` check in the same place (in the
same function where `logger` and `reason` are used) but replace the logged
payload so no raw `reason` text is emitted while preserving ability to correlate
events via the checksum/length metadata.

@codecov-commenter
Copy link

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 45.01511% with 182 lines in your changes missing coverage. Please review.

❌ Your patch status has failed because the patch coverage (45.0%) is below the target coverage (80.0%). You can increase the patch coverage or adjust the target coverage.
❌ Your project status has failed because the head coverage (68.7%) is below the target coverage (80.0%). You can increase the head coverage or adjust the target coverage.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

@@           Coverage Diff           @@
##            main     #52     +/-   ##
=======================================
- Coverage   68.7%   68.7%   -0.1%     
=======================================
  Files        154     154             
  Lines       9709    9708      -1     
=======================================
- Hits        6677    6675      -2     
- Misses      3032    3033      +1     

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 267e19f...d03f5d3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants