Skip to content

fix: validate review model responses - #3372

Merged
IsmaelMartinez merged 7 commits into
The-PR-Agent:mainfrom
utsab345:fix/review-model-validation
Sep 15, 2026
Merged

IsmaelMartinez merged 7 commits into
The-PR-Agent:mainfrom
utsab345:fix/review-model-validation

Conversation

@utsab345

Copy link
Copy Markdown
Contributor

Summary

Fixes #3355.

Validate parsed /review responses with the PRReview output model before rendering and while processing review chunks. Invalid responses emit one structured warning containing the first field path and value, while the existing rendering flow and markdown remain unchanged.

Unknown review fields are rejected by the output model so misspelled keys are reported. Added tests cover invalid and valid responses and verify unchanged markdown output.

Validation

  • PYTHONPATH=. uv run pytest tests/unittest/test_output_models.py tests/unittest/test_pr_reviewer_core.py tests/unittest/test_review_large_diff_chunking.py tests/unittest/test_pr_reviewer_prompt_contract.py -q --color=no
  • uv run pre-commit run --files pr_agent/algo/output_models.py pr_agent/tools/pr_reviewer.py tests/unittest/test_pr_reviewer_core.py

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Validate AI review responses against typed output contracts

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds typed Pydantic contracts for prompt-generated outputs.
• Validates review responses during chunk handling and before rendering.
• Logs first schema violation while preserving existing markdown output.
Diagram

graph TD
  A["Model response"] --> B["YAML parser"] --> C["PRReview schema"] --> D{"Schema valid?"}
  D -->|Yes| E["Review processing"] --> F["Markdown renderer"]
  D -->|No| G["Structured warning"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fail closed on invalid reviews
  • ➕ Prevents malformed model output from reaching downstream consumers.
  • ➕ Provides stronger runtime guarantees than warning-only validation.
  • ➖ Discards potentially useful review content for minor schema deviations.
  • ➖ Changes established rendering behavior and could regress model compatibility.
2. Validate inside the YAML parser
  • ➕ Centralizes parsing and schema validation in one operation.
  • ➕ Avoids explicit validation calls in each review path.
  • ➖ Couples a generic YAML recovery helper to the review schema.
  • ➖ Makes gradual adoption by other prompt outputs more difficult.

Recommendation: Keep the compatibility-first approach: validate at review-processing boundaries, emit actionable diagnostics, and preserve rendering. Fail-closed behavior can be introduced later behind configuration once model conformance is measured.

Files changed (6) +509 / -9

Enhancement (1) +172 / -0
output_models.pyDefine typed contracts for prompt-generated outputs +172/-0

Define typed contracts for prompt-generated outputs

• Adds Pydantic models for review, description, suggestion, ranking, labeling, and documentation prompt responses. Review contracts reject unknown fields and constrain enumerations, scores, list sizes, and aliased prompt fields.

pr_agent/algo/output_models.py

Bug fix (2) +23 / -3
pr_evaluate_prompt_response.tomlCorrect the response-ranking model name +2/-2

Correct the response-ranking model name

• Fixes the misspelled PRRankResponses class name so the evaluation prompt matches the runtime output model.

pr_agent/settings/pr_evaluate_prompt_response.toml

pr_reviewer.pyValidate parsed reviews without changing rendering +21/-1

Validate parsed reviews without changing rendering

• Validates parsed review data during chunk processing and before final rendering. Schema failures emit one structured warning containing the first field path and invalid value while allowing the existing flow to continue.

pr_agent/tools/pr_reviewer.py

Tests (2) +308 / -0
test_output_models.pyTest output contracts and prompt synchronization +268/-0

Test output contracts and prompt synchronization

• Covers valid fixtures and boundary failures across all new output models. Adds contract checks ensuring prompt-declared fields and annotations remain synchronized with their Pydantic counterparts.

tests/unittest/test_output_models.py

test_pr_reviewer_core.pyVerify review validation warning behavior +40/-0

Verify review validation warning behavior

• Confirms invalid review output logs the expected field and value without altering rendered markdown. Also verifies valid output produces no validation warning.

tests/unittest/test_pr_reviewer_core.py

Other (1) +6 / -6
pr_reviewer_prompts.tomlTighten review prompt output constraints +6/-6

Tighten review prompt output constraints

• Uses literal choices and integer types for constrained review fields. Aligns ticket-compliance examples with the typed requires_further_human_verification field.

pr_agent/settings/pr_reviewer_prompts.toml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (2) 🎨 UX issues (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Partial review issues disappear 📎 Requirement gap ≡ Correctness
Description
convert_to_markdown_v2 now skips an issue unless relevant_file, issue_header, and
issue_content are all present as strings. When a model omits one of these fields, schema
validation remains warning-only but the guard suppresses the issue that the previous renderer would
display using an empty fallback.
Code

pr_agent/algo/utils.py[R485-488]

+                        if not all(isinstance(issue.get(field), str) for field in (
+                            'relevant_file', 'issue_header', 'issue_content'
+                        )):
+                            continue
Evidence
Compliance item 6 requires validation failures not to alter existing review processing, while item 8
requires rendered review content to remain unchanged. The added guard skips entries with omitted
text fields, although those fields previously used empty-string defaults and continued through
rendering.

Validate parsed reviews against the PRReview model
Preserve output and silence for valid reviews
pr_agent/algo/utils.py[482-493]
pr_agent/algo/output_models.py[16-23]

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

## Issue description
The new issue-field guard changes existing Markdown behavior for omitted text fields, even though review schema validation is required to remain warning-only and preserve processing.
## Fix Focus Areas
- pr_agent/algo/utils.py[485-493]
## Recommended Fix
Retain the previous empty-string fallback for missing `relevant_file`, `issue_header`, and `issue_content` fields while continuing to skip explicitly supplied non-string values. Add coverage proving that an issue with an omitted text field produces the same Markdown as before validation was introduced.

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


2. Wrong text types hide review findings 📎 Requirement gap ☼ Reliability
Description
_validate_review_schema warns about a non-string nested issue field, but _prepare_pr_review
immediately passes the unchanged mapping to convert_to_markdown_v2, where calls such as
issue_content.strip() can raise. When a model returns an integer for
review.key_issues_to_review.0.issue_content, the structured warning is followed by a `Failed to
process 'Recommended focus areas for review'` exception log, presenting the validation failure as a
rendering error.
Code

pr_agent/tools/pr_reviewer.py[R952-953]

+        if self.prediction_data is None:
+            self._validate_review_schema(data)
Evidence
Rule 7 requires validation failures to produce one structured warning and not surface as rendering
errors. The new validation call leaves malformed data unchanged, while the renderer invokes
.strip() on nested issue values inside an exception handler that logs a rendering failure.

Warn once with structured validation details
pr_agent/tools/pr_reviewer.py[951-954]
pr_agent/algo/utils.py[481-514]
pr_agent/algo/output_models.py[19-23]

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

## Issue description
Malformed nested review text fields are correctly detected by schema validation, but the unchanged data can subsequently cause renderer exceptions, contrary to the requirement that validation failures produce one structured warning without surfacing as rendering errors.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[952-953]
- pr_agent/algo/utils.py[481-514]
## Recommended Fix
Make malformed key-issue field handling non-throwing in the existing renderer while preserving its pre-validation Markdown result. Check nested issue values before calling string methods and silently skip unusable entries after the schema warning, without adding another warning or exception log.

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


3. Two review comment helpers are unused 📘 Rule violation ⚙ Maintainability
Description
The newly added _get_previous_review_comment and _remove_previous_review_comment methods have no
callers anywhere in the repository. Because no review path references either helper, their provider
calls and exception handling form a second, inactive comment-management surface that later changes
could mistakenly maintain.
Code

pr_agent/tools/pr_reviewer.py[R1238-1241]

+    def _get_previous_review_comment(self):
+        """
+        Get the previous review comment if it exists.
+        """
Evidence
Compliance rule 2 requires all submitted code to be active and necessary. Repository-wide reference
checks find only these method definitions, while the cited branch regions show both newly added
inactive helpers.

Rule 2: No Dead or Commented-Out Code
pr_agent/tools/pr_reviewer.py[1238-1250]
pr_agent/tools/pr_reviewer.py[1252-1260]

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

## Issue description
The newly added previous-review comment helpers are not called anywhere and therefore constitute dead code.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[1238-1260]
## Recommended Fix
Remove `_get_previous_review_comment` and `_remove_previous_review_comment`. If they were intended to replace existing inline behavior, wire them into that behavior and add tests proving both helpers are used instead of retaining inactive methods.

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


View high (15)
4. Partial reviews close active findings ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
_prepare_review_finding_state no longer includes review_failed_chunk_count among the conditions
that prevent finding resolution. When one large-diff chunk fails but a sibling succeeds and no files
are excluded by the token budget, the failed chunk's findings are absent from the merged partial
review and reconciliation marks prior active findings as resolved even though they were never
rechecked.
Code

pr_agent/tools/pr_reviewer.py[733]

-            and not bool(self.review_failed_chunk_count)
Evidence
The chunk loop omits failed responses, records their count, and still merges successful sibling
chunks, while reconciliation resolves previous findings absent from the merged current set whenever
allow_resolution is true. Because the current resolution gate omits the failed-chunk count and
remaining_files_list covers only files excluded during packing—not files included in a chunk whose
model call failed—the failed count is retained only for coverage messaging rather than lifecycle
completeness, contrary to the warn-only validation requirement that existing chunk processing and
publication behavior remain unchanged.

Warn-only PRReview validation after YAML parsing
pr_agent/tools/pr_reviewer.py[733-742]
pr_agent/tools/pr_reviewer.py[813-844]
pr_agent/algo/review_finding_state.py[267-280]
pr_agent/tools/pr_reviewer.py[813-843]
pr_agent/tools/pr_reviewer.py[733-750]
pr_agent/algo/review_finding_state.py[230-237]
pr_agent/algo/review_finding_state.py[267-291]
pr_agent/tools/pr_reviewer.py[982-990]
pr_agent/algo/review_finding_state.py[267-290]

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

## Issue description
A partially successful chunked review is treated as complete for lifecycle reconciliation and can resolve persistent findings that are absent only because their chunk failed.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[733-742]
- tests/unittest/test_review_large_diff_chunking.py[169-192]
## Recommended Fix
Restore `not bool(self.review_failed_chunk_count)` as a completeness guard in the `allow_resolution` conditions so resolution remains disabled whenever any chunk fails. Reinstate a regression test proving that a failed chunk preserves prior active findings and records or marks the review run as partial/incomplete until every chunk has been successfully reviewed.

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


5. GitHub incremental reviews fail ✓ Resolved 📘 Rule violation ☼ Reliability
Description
_can_run_incremental_review constructs recent_commits_threshold with naive
datetime.datetime.now() and compares it directly with the provider-derived
last_seen_commit_date, which GitHub supplies as a timezone-aware author timestamp. On a subsequent
incremental run with a populated last_seen_commit, evaluating all_commits_too_recent raises
TypeError before review generation and sends the command into the failed-review path instead of
publishing feedback.
Code

pr_agent/tools/pr_reviewer.py[R1285-1286]

+        recent_commits_threshold = datetime.datetime.now() - datetime.timedelta(
   minutes=get_settings().pr_reviewer.minimal_minutes_for_incremental_review
Evidence
GitHub stores provider commit objects as last_seen_commit, and the reviewer reads their author
timestamps directly as timezone-aware PyGithub dates, while the incremental-review gate creates a
naive threshold for the comparison. The regression test uses an aware UTC commit timestamp and
documents that comparing it with a naive datetime.now() threshold raises TypeError on an
applicable second run, matching the current implementation's direct comparison.

Rule 3: Robust Error Handling
pr_agent/tools/pr_reviewer.py[1285-1292]
pr_agent/git_providers/github_provider.py[230-240]
tests/unittest/test_github_incremental_default.py[62-88]
pr_agent/tools/pr_reviewer.py[1285-1293]
pr_agent/tools/pr_reviewer.py[270-274]

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

## Issue description
Incremental GitHub reviews compare a timezone-aware commit date with a naive threshold, causing the review command to fail before generating or publishing feedback. The comparison must also remain compatible with providers that expose naive UTC timestamps.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[1285-1293]
- tests/unittest/test_github_incremental_default.py[62-88]
## Recommended Fix
Build the recency threshold in UTC and normalize `last_seen_commit_date` to the same representation before comparing. Either convert aware provider timestamps to naive UTC while preserving already-naive UTC values, or keep both values timezone-aware in UTC; restore or add a regression test using a timezone-aware GitHub commit date and retain coverage for naive provider timestamps.

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


6. Host time shifts incremental reviews ✓ Resolved 🐞 Bug ≡ Correctness
Description
_can_run_incremental_review now builds recent_commits_threshold from local wall-clock time while
GitLab and Azure DevOps expose naive timestamps normalized to UTC. On hosts outside UTC, the
timezone offset changes whether a commit passes the minimum-age gate, causing reviews to run too
early or be skipped too long.
Code

pr_agent/tools/pr_reviewer.py[R1285-1287]

+        recent_commits_threshold = datetime.datetime.now() - datetime.timedelta(
   minutes=get_settings().pr_reviewer.minimal_minutes_for_incremental_review
)
Evidence
GitLab and Azure DevOps explicitly normalize offset-bearing dates to naive UTC, but the reviewer
compares them against naive local datetime.now(). Therefore the same commit and configuration
produce different gate decisions depending on the process host timezone.

pr_agent/tools/pr_reviewer.py[1285-1293]
pr_agent/git_providers/gitlab_provider.py[49-70]
pr_agent/git_providers/azuredevops_provider.py[88-111]

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

## Issue description
Incremental review age thresholds use host-local time even though supported providers expose commit dates normalized to UTC.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[1285-1293]
- pr_agent/git_providers/gitlab_provider.py[49-70]
- pr_agent/git_providers/azuredevops_provider.py[88-111]
## Recommended Fix
Construct the threshold from UTC time and compare timestamps in one normalized UTC representation. Add coverage with a non-UTC host timezone to verify provider-independent threshold decisions.

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


7. Two providers lose finding state ✓ Resolved 🐞 Bug ≡ Correctness
Description
_review_finding_state_enabled rejects every provider that inherits
GitProvider.publish_persistent_comment, even when the base implementation delegates to the full
editable persistent-comment flow. GitLab and configured Azure DevOps providers advertise
finding-state support and implement comment editing, but this guard prevents their existing state
from being loaded or reconciled.
Code

pr_agent/tools/pr_reviewer.py[R467-470]

+        publisher = getattr(provider, "publish_persistent_comment", None)
+        if getattr(publisher, "__func__", None) is GitProvider.publish_persistent_comment:
+            # Skip generic publishers; they only create comments and cannot safely carry lifecycle state.
+            return False
Evidence
The base publisher calls publish_persistent_comment_full whenever the provider implements editing;
it does not merely create a comment. GitLab always declares finding-state support and implements
editing, while Azure DevOps declares support when stable agent identities are configured, so the new
identity check disables capabilities those providers intentionally expose.

pr_agent/tools/pr_reviewer.py[456-484]
pr_agent/tools/pr_reviewer.py[688-705]
pr_agent/git_providers/git_provider.py[618-647]
pr_agent/git_providers/gitlab_provider.py[934-984]
pr_agent/git_providers/azuredevops_provider.py[1018-1049]

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

## Issue description
The inherited persistent publisher is rejected even for providers that can edit comments and explicitly support persistent finding state.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[456-484]
- pr_agent/git_providers/git_provider.py[618-647]
- pr_agent/git_providers/gitlab_provider.py[934-984]
- pr_agent/git_providers/azuredevops_provider.py[1018-1049]
## Recommended Fix
Remove the method-identity exclusion and gate state handling using the provider's explicit finding-state support, issue-comment capability, and effective comment-editing capability. Add provider tests covering inherited persistent publication for GitLab and Azure DevOps.

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


8. Inverted ranges hide the invalid field ✓ Resolved 📎 Requirement gap ◔ Observability
Description
_validate_line_range raises a model-level ValueError, so Pydantic reports the issue-list entry
as the location and the whole entry as the input rather than end_line and its value. When an
issue's end line precedes its start line, _validate_review_schema therefore emits its structured
warning without the specific failing field path or invalid value.
Code

pr_agent/algo/output_models.py[R25-28]

+    @model_validator(mode="after")
+    def _validate_line_range(self):
+        if self.end_line < self.start_line:
+            raise ValueError("end_line must be greater than or equal to start_line")
Evidence
Rule 6 requires the single structured warning to identify the failing field path and value. The
added model-level validator attaches an inverted-range failure to the enclosing issue model,
preventing the warning builder from extracting the specific end_line location and value.

Warn on malformed parsed review responses without changing publication output
pr_agent/algo/output_models.py[25-28]

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

## Issue description
Inverted issue line ranges produce a model-level validation error, causing the warning artifact to contain the whole issue entry rather than the failing `end_line` path and value.
## Fix Focus Areas
- pr_agent/algo/output_models.py[25-28]
- tests/unittest/test_output_models.py[163-173]
## Recommended Fix
Replace the model-level range validator with an `end_line` field validator that compares against the already validated `start_line`. Add a warning test confirming an inverted range reports `review.key_issues_to_review.0.end_line` and the submitted end-line value.

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


9. Malformed task locations pass silently ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
TodoSection.line_number uses an unconstrained, coercive int, so booleans, numeric strings, zero,
and negative values pass PRReview.model_validate without the required structured
malformed-response warning. When TODO scanning is enabled, _validate_review_schema accepts these
locations and format_todo_item passes the unchanged raw value to provider line-link generation and
renders it in the TODO file reference.
Code

pr_agent/algo/output_models.py[36]

+    line_number: int
Evidence
The new TODO output model validates line_number as an unconstrained coercive integer even though
it represents a source line, unlike the parallel key-issue location model, which requires a value of
at least one. Because schema validation does not reject these malformed values or replace the
original mapping, no structured warning is emitted, and the renderer reads the raw number directly
for both provider line-link generation and the displayed TODO location.

Warn on malformed parsed PR review responses without changing output
pr_agent/algo/output_models.py[32-37]
pr_agent/algo/utils.py[2078-2084]
pr_agent/settings/pr_reviewer_prompts.toml[70-74]
pr_agent/tools/pr_reviewer.py[882-896]
pr_agent/algo/utils.py[2070-2084]
pr_agent/algo/output_models.py[16-29]
pr_agent/algo/utils.py[2079-2089]

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

## Issue description
`TodoSection.line_number` accepts booleans, numeric strings, and non-positive integers, allowing malformed TODO source locations to pass review schema validation without a structured warning and reach rendering and line-link generation.
## Fix Focus Areas
- pr_agent/algo/output_models.py[32-37]
- tests/unittest/test_output_models.py[147-160]
## Recommended Fix
Declare `TodoSection.line_number` as `StrictInt = Field(ge=1)`, matching the validation used for key-issue line locations. Add regression tests proving that zero, negative integers, booleans, and numeric strings are rejected.

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


10. Boolean lines hide review findings ✓ Resolved 🐞 Bug ≡ Correctness
Description
KeyIssuesComponentLink.start_line and end_line use coercive int fields, so
_validate_review_schema accepts booleans even though the renderer continues using the unconverted
raw mapping. When a model emits true for either location, int(str(True)) raises during Markdown
conversion and the surrounding exception handler omits that issue from the published review.
Code

pr_agent/algo/output_models.py[R22-23]

+    start_line: int
+    end_line: int
Evidence
The model uses unrestricted integers for both locations, while the same module explicitly uses
StrictInt where coercible values must be rejected. Validation discards the validated model and
passes the original mapping onward; Markdown rendering stringifies each raw line before integer
conversion, and its catch block skips the failed issue entirely.

pr_agent/algo/output_models.py[22-23]
pr_agent/algo/output_models.py[56-63]
pr_agent/tools/pr_reviewer.py[930-933]
pr_agent/algo/utils.py[505-538]

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

## Issue description
Review issue locations use coercive integers, allowing booleans and numeric strings to pass schema validation even though rendering consumes the raw values and can omit the finding.
## Fix Focus Areas
- pr_agent/algo/output_models.py[22-23]
- tests/unittest/test_output_models.py[147-192]
## Recommended Fix
Declare both location fields as `StrictInt` with a minimum value of 1, validate that `end_line` is not before `start_line`, and add rejection tests for booleans, numeric strings, non-positive lines, and inverted ranges.

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


11. Wrong-typed reviews publish silently ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
_load_valid_review_yaml accepts any non-empty review mapping without calling
PRReview.model_validate, and PRReviewer lacks the _validate_review_schema method exercised by
the new tests. Responses with missing, misspelled, wrongly typed, out-of-range, or unknown
fields—including a scalar key_issues_to_review—therefore proceed through normal or chunk
processing to structured publication and Markdown rendering without the required structured warning.
Code

pr_agent/tools/pr_reviewer.py[R902-905]

+        data = cls._load_review_yaml(prediction)
+        if not isinstance(data, dict) or not isinstance(data.get("review"), dict) or not data["review"]:
+            raise ValueError(f"{source} did not contain a non-empty review mapping")
+        return data
Evidence
The helper checks only that the parsed review is a non-empty mapping and never references
PRReview, even though that model defines forbidden extras, required nested issue data, strict
integers, bounds, and literals. The downstream paths publish and render the unchanged mapping for
both normal and chunked reviews, while the added tests call _validate_review_schema despite no
such implementation existing, demonstrating that the required warning-only schema-validation path is
absent.

Validate parsed reviews against the PRReview model without changing output
pr_agent/tools/pr_reviewer.py[899-905]
pr_agent/algo/output_models.py[77-80]
pr_agent/tools/pr_reviewer.py[912-939]
pr_agent/algo/output_models.py[52-80]
tests/unittest/test_pr_reviewer_core.py[256-279]
pr_agent/tools/pr_reviewer.py[912-921]

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

## Issue description
Parsed normal and chunked review responses are only checked for a non-empty mapping and are never validated against `PRReview`, leaving the output contract inert and allowing malformed fields to reach structured publishing and Markdown rendering without the required warning. The validation tests also exercise a `_validate_review_schema` method that `PRReviewer` does not define.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[899-905]
- pr_agent/tools/pr_reviewer.py[912-939]
- pr_agent/algo/output_models.py[52-80]
- tests/unittest/test_pr_reviewer_core.py[256-279]
## Recommended Fix
Import `PRReview` and Pydantic's validation error type, implement `_validate_review_schema`, and invoke it immediately after YAML parsing for both normal and chunk responses, before structured publishing or rendering. Validate with `PRReview.model_validate`, apply the configuration-controlled required-field checks, and catch validation failures so that exactly one structured warning reports the first error's dotted field path and offending value. Preserve the original parsed dictionary and existing rendering behavior after warning.

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


12. Malformed reviews can block publication ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
_load_valid_review_yaml raises ValueError when the parsed review is empty or has the wrong outer
shape instead of treating validation as warning-only. The normal path invokes this helper before
storing the prediction and the chunk path re-raises its error, so malformed model output triggers
retries or total review failure rather than continuing through the existing rendering flow.
Code

pr_agent/tools/pr_reviewer.py[R903-904]

+        if not isinstance(data, dict) or not isinstance(data.get("review"), dict) or not data["review"]:
+            raise ValueError(f"{source} did not contain a non-empty review mapping")
Evidence
Compliance rule 6 explicitly requires strict behavior to remain disabled and validation not to block
existing processing or publication. The helper raises for malformed review shapes, and both newly
modified call sites use that exception to abort the current model attempt.

Validate parsed reviews against the PRReview model without changing output
pr_agent/tools/pr_reviewer.py[797-799]
pr_agent/tools/pr_reviewer.py[841-848]
pr_agent/tools/pr_reviewer.py[899-905]

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

## Issue description
The new review-shape gate raises on malformed responses, allowing validation to trigger fallback attempts or fail review publication even though validation must remain warning-only.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[797-799]
- pr_agent/tools/pr_reviewer.py[841-848]
- pr_agent/tools/pr_reviewer.py[899-905]
## Recommended Fix
Remove the blocking exception behavior from validation. Parse and retain the original response, emit one structured warning for the first model validation error, and let both normal and chunked reviews continue through the same processing and rendering behavior used without validation.

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


13. Review commands fail during import ✓ Resolved 🐞 Bug ≡ Correctness
Description
pr_reviewer imports PreparedPRDiff, but pr_processing defines no such symbol, and the new
calls pass return_prepared and prepared_diff keywords that its helper functions do not accept.
Loading any review command therefore raises ImportError before execution, and adding only the
missing symbol still lets the chunking path reach a TypeError.
Code

pr_agent/tools/pr_reviewer.py[21]

+    PreparedPRDiff,
Evidence
The changed reviewer imports and uses PreparedPRDiff, while pr_processing contains no definition
for that type; additionally, the existing get_pr_diff and get_pr_multi_diffs signatures omit the
newly supplied return_prepared and prepared_diff keyword arguments. Together, these citations
show that the reviewer consumes a prepared-diff API that the producer module does not implement.

pr_agent/tools/pr_reviewer.py[20-25]
pr_agent/tools/pr_reviewer.py[771-821]
pr_agent/algo/pr_processing.py[45-50]
pr_agent/algo/pr_processing.py[394-399]

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

## Issue description
`pr_reviewer` depends on a prepared-diff type and keyword-argument contract that `pr_processing` does not implement, preventing the review tool from loading and causing chunked reviews to fail even if only the missing import is addressed.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[21-21]
- pr_agent/tools/pr_reviewer.py[777-779]
- pr_agent/tools/pr_reviewer.py[815-821]
- pr_agent/algo/pr_processing.py[45-50]
- pr_agent/algo/pr_processing.py[394-399]
## Recommended Fix
Add `PreparedPRDiff` to `pr_processing`, extend `get_pr_diff` to support `return_prepared`, and extend `get_pr_multi_diffs` to accept `prepared_diff` and reuse the prepared file data without fetching and preparing the diff again. Preserve the diff, omitted-file list, reusable prepared file data, and all existing return contracts when the new arguments are omitted; alternatively, remove the new prepared-diff import and calls until the helper API can be implemented together.

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


14. Default test reviews log false warnings 🐞 Bug ≡ Correctness
Description
The primary example now emits relevant_tests: No as an unquoted YAML scalar, which load_yaml()
parses with yaml.safe_load() as the boolean False rather than the required `Literal["Yes",
"No"]` string. Because test review is enabled by default, a model following this displayed example
reaches _validate_review_schema() and produces the malformed-output warning that valid
prompt-conforming responses are meant to avoid.
Code

pr_agent/settings/pr_reviewer_prompts.toml[172]

+  relevant_tests: No
Evidence
The changed example provides an unquoted No, while the new output model only allows string
literals. The review loader uses PyYAML's safe loader, and test-review output is enabled in the
default reviewer configuration, so this example can produce a warning in normal /review runs.

pr_agent/settings/pr_reviewer_prompts.toml[118-118]
pr_agent/algo/output_models.py[64-64]
pr_agent/algo/utils.py[1010-1032]
pr_agent/settings/configuration.toml[115-121]

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

## Issue description
The primary `/review` prompt example renders `relevant_tests: No` unquoted. PyYAML resolves this YAML 1.1 boolean spelling to `False`, but `Review.relevant_tests` accepts only the strings `"Yes"` and `"No"`, causing a false schema-validation warning for output following the example.
## Fix Focus Areas
- pr_agent/settings/pr_reviewer_prompts.toml[172-172]
## Recommended Fix
Quote the example value as `relevant_tests: "No"`, or use the existing block-scalar form, so parsing preserves a string that satisfies the output model.

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


15. Missing fields log the wrong value ✓ Resolved 📎 Requirement gap ◔ Observability
Description
_validate_review_schema records first_error["input"], which Pydantic sets to the containing
object rather than the absent field for a missing error. When review, key_issues_to_review, or
a nested required property is omitted, the warning reports a dictionary as its value instead of
identifying the value as absent.
Code

pr_agent/tools/pr_reviewer.py[R890-893]

+                artifact={
+                    "field": field_path,
+                    "value": first_error.get("input"),
+                },
Evidence
Compliance rule 7 requires the structured warning to identify the failing path and offending value.
The warning always copies Pydantic's error input, which is the parent object for missing-field
errors and therefore does not represent the absent field's value.

Log one structured warning for malformed model responses
pr_agent/tools/pr_reviewer.py[885-893]
pr_agent/algo/output_models.py[66-66]

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

## Issue description
Schema warnings for missing fields currently report the containing object as the offending value because they copy Pydantic's `input` property directly.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[885-893]
- tests/unittest/test_pr_reviewer_core.py[254-266]
## Recommended Fix
When the first validation error has type `missing`, emit `None` as the artifact value; retain the error input for failures involving an actual supplied value. Add coverage for a required model field such as `key_issues_to_review` being absent.

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


16. Two prompt aliases are dead code ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
relevant_section and file_idx_and_path are assigned at module scope, but no production or test
code imports or references either alias. The prompt-contract test maps lowercase prompt declarations
directly to RelevantSection and FileIdxAndPath, so these assignments add an unused second naming
surface.
Code

pr_agent/algo/output_models.py[R185-187]

+# Prompt templates use these lowercase names in their class declarations.
+relevant_section = RelevantSection
+file_idx_and_path = FileIdxAndPath
Evidence
Compliance rule 2 requires submitted code to be active and necessary. The aliases are only defined
in the output-model module, while the contract test directly associates each lowercase prompt
declaration with its capitalized model class.

Rule 2: No Dead or Commented-Out Code
pr_agent/algo/output_models.py[185-187]
tests/unittest/test_output_models.py[258-260]

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

## Issue description
Two lowercase class aliases are introduced without any consumer, while the prompt-contract tests already resolve those prompt names explicitly.
## Fix Focus Areas
- pr_agent/algo/output_models.py[185-187]
## Recommended Fix
Delete the two unused aliases and their explanatory comment. Keep the explicit lowercase-to-model mappings in the prompt-contract tests, which already provide the required name translation.

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


17. Wrong numeric types evade warnings ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
Review.score and the other integer fields use Pydantic's coercive int validation, so values such
as "89" or true validate without reaching the warning handler. When a parsed review supplies a
numeric field with the wrong YAML type, the malformed value continues into merging and rendering as
though its declared type were correct.
Code

pr_agent/algo/output_models.py[63]

+    score: Optional[int] = Field(default=None, ge=0, le=100)
Evidence
Compliance rule 6 requires wrong value types to be detected and warned about. The newly added model
declares score as int but does not enable strict validation, so Pydantic can coerce numeric
strings and booleans instead of raising the ValidationError that triggers the structured warning.

Warn on malformed parsed PR review responses without changing output
pr_agent/algo/output_models.py[52-69]

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

## Issue description
Review model integer fields currently use coercive Pydantic validation, allowing wrong-typed values such as numeric strings and booleans to pass without the required structured warning.
## Fix Focus Areas
- pr_agent/algo/output_models.py[52-75]
- pr_agent/settings/pr_reviewer_prompts.toml[155-174]
- tests/unittest/test_pr_reviewer_core.py[214-266]
## Recommended Fix
Validate review fields with strict types, update numeric prompt examples so they are emitted as YAML numbers rather than block scalars, and add tests proving wrong-typed numeric values produce exactly one warning while correctly typed values do not.

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


18. Valid reviews emit false warnings ✓ Resolved 🐞 Bug ≡ Correctness
Description
Review.relevant_tests accepts only the exact strings Yes and No, while the review prompt
requests | block scalars whose parsed values retain a trailing newline. With test review enabled
by default, ordinary prompt-compliant values such as No\n fail _validate_review_schema; enabled
risk and merge recommendation fields have the same exact-literal mismatch.
Code

pr_agent/algo/output_models.py[64]

+    relevant_tests: Optional[Literal["Yes", "No"]] = None
Evidence
The prompt renders relevant_tests using |, and load_yaml passes yaml.safe_load results
through unchanged. Existing renderer tests represent these parsed prompt values as No\n, whereas
the new model restricts the field to the exact literals Yes and No; the default configuration
enables this field for normal reviews.

pr_agent/settings/pr_reviewer_prompts.toml[172-176]
pr_agent/algo/utils.py[1018-1031]
tests/unittest/test_convert_to_markdown.py[50-54]
pr_agent/settings/configuration.toml[118-118]
pr_agent/algo/output_models.py[59-64]

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

## Issue description
Prompt-compliant YAML block scalars retain trailing newlines, so exact `Literal` fields reject otherwise valid review responses and emit false schema warnings.
## Fix Focus Areas
- pr_agent/algo/output_models.py[59-64]
- pr_agent/tools/pr_reviewer.py[882-895]
## Recommended Fix
Normalize surrounding whitespace for the review model's exact string fields before literal validation, while preserving rejection of values outside the declared choices. Add tests that pass values as parsed from `|` block scalars, including `"No\n"`, `"low\n"`, and `"safe_to_merge\n"`.

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


  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread pr_agent/tools/pr_reviewer.py Outdated
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/algo/output_models.py Outdated
Comment thread pr_agent/algo/output_models.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ee731e3

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 673a5bd

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from 673a5bd to 54a43f2 Compare September 14, 2026 10:39
Comment thread pr_agent/algo/output_models.py Outdated
Comment thread pr_agent/algo/output_models.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 54a43f2

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from 54a43f2 to 78d175d Compare September 14, 2026 10:48
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/algo/output_models.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 78d175d

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from 78d175d to 17004c2 Compare September 14, 2026 11:00
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from 17004c2 to a6d6ffb Compare September 14, 2026 11:05
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a6d6ffb

@IsmaelMartinez IsmaelMartinez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for landing this within the hour of the issue and for working every Qodo thread into the branch. The helper holds: one warning per response, nothing raised, data untouched, markdown byte-identical, chunked path covered.

Two things before merge. It inherits #3371's bare No example, which parses as a boolean and would warn on every review, so that fix lands first. And #3359 rewrites the same chunked-loop lines with the earlier number, so it lands first too and this rebases onto its _load_valid_review_yaml, the single parse site where the warn-only call belongs. When rebasing, keep the original chunk warning so the chunk index stays in the log.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

{%- endif %}
relevant_tests: |
No
relevant_tests: No

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Default test reviews log false warnings 🐞 Bug ≡ Correctness

The primary example now emits relevant_tests: No as an unquoted YAML scalar, which load_yaml()
parses with yaml.safe_load() as the boolean False rather than the required `Literal["Yes",
"No"]` string. Because test review is enabled by default, a model following this displayed example
reaches _validate_review_schema() and produces the malformed-output warning that valid
prompt-conforming responses are meant to avoid.
Agent Prompt
## Issue description
The primary `/review` prompt example renders `relevant_tests: No` unquoted. PyYAML resolves this YAML 1.1 boolean spelling to `False`, but `Review.relevant_tests` accepts only the strings `"Yes"` and `"No"`, causing a false schema-validation warning for output following the example.

## Fix Focus Areas
- pr_agent/settings/pr_reviewer_prompts.toml[172-172]

## Recommended Fix
Quote the example value as `relevant_tests: "No"`, or use the existing block-scalar form, so parsing preserves a string that satisfies the output model.

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit d79d7b0

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from d79d7b0 to b4c1d4e Compare September 14, 2026 14:38
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/tools/pr_reviewer.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit b4c1d4e

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from b4c1d4e to a6d6ffb Compare September 14, 2026 15:18
Comment thread pr_agent/algo/output_models.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a6d6ffb

Comment thread pr_agent/algo/output_models.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit db8e89c

Comment thread pr_agent/algo/output_models.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a3ecad4

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a6a6194

Comment thread pr_agent/tools/pr_reviewer.py Fixed
Comment thread pr_agent/tools/pr_reviewer.py
Comment thread pr_agent/tools/pr_reviewer.py Outdated
Comment thread pr_agent/tools/pr_reviewer.py Outdated
Comment thread pr_agent/tools/pr_reviewer.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 919b678

Comment thread pr_agent/tools/pr_reviewer.py Outdated
Comment on lines +1238 to +1241
def _get_previous_review_comment(self):
"""
Get the previous review comment if it exists.
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Two review comment helpers are unused 📘 Rule violation ⚙ Maintainability

The newly added _get_previous_review_comment and _remove_previous_review_comment methods have no
callers anywhere in the repository. Because no review path references either helper, their provider
calls and exception handling form a second, inactive comment-management surface that later changes
could mistakenly maintain.
Agent Prompt
## Issue description
The newly added previous-review comment helpers are not called anywhere and therefore constitute dead code.

## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[1238-1260]

## Recommended Fix
Remove `_get_previous_review_comment` and `_remove_previous_review_comment`. If they were intended to replace existing inline behavior, wire them into that behavior and add tests proving both helpers are used instead of retaining inactive methods.

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 1542444

@IsmaelMartinez IsmaelMartinez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rebase attempt and the partial-run state fix.

This head must not merge as it stands: the pr_reviewer.py conflict was resolved by keeping the pre-#3359 file, so the merged tree reverts #3359's fallback retry, #3361's is_supported guard, #3291's PreparedPRDiff reuse and #3371's relevant_tests block scalar, and build-and-test is red for exactly that reason. 1542444 then re-adds parts of main by hand, which a clean rebase makes unnecessary.

Recovery: git reset --hard a6a6194f, squash, then git rebase --onto upstream/main 6aa533db. Two hunks conflict. In the chunk loop keep main's data = self._load_valid_review_yaml(prediction, source=f"review chunk {chunk_index + 1}") and add self._validate_review_schema(data) under it. In test_a_malformed_chunk_fails_the_model_attempt keep main's assert reviewer.prediction_data is None. That shape passed the full suite here, and I will approve on it.

@utsab345
utsab345 force-pushed the fix/review-model-validation branch from 1542444 to 3967890 Compare September 14, 2026 16:34
Comment thread pr_agent/tools/pr_reviewer.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 3967890

Comment thread pr_agent/algo/utils.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 908f7d2

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ee99712

@utsab345

Copy link
Copy Markdown
Contributor Author

@IsmaelMartinez Done. All requested fixes are applied, the branch is rebased cleanly, and the focused tests are passing.

@IsmaelMartinez IsmaelMartinez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for following the reset and rebase to the letter, and for folding Qodo's two renderer threads into the same push. Approving.

The merged tree touches only your five files with nothing from main reverted, the chunk loop carries main's parse plus the schema check, and the markdown is byte-identical with or without the new guard.

The two open Qodo threads on utils.py are answered by the last two commits; I will resolve them on merge.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/review cannot tell a malformed model response from a valid one until publication

3 participants