Skip to content

fix: retry only failed chunks in large PR reviews - #3402

Merged
IsmaelMartinez merged 14 commits into
The-PR-Agent:mainfrom
utsab345:fix/3397-retry-failed-chunks
Sep 16, 2026
Merged

IsmaelMartinez merged 14 commits into
The-PR-Agent:mainfrom
utsab345:fix/3397-retry-failed-chunks

Conversation

@utsab345

@utsab345 utsab345 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

References to other Issues or PRs

Fixes #3397

Brief description of what is fixed or changed

Large PR reviews can be split into multiple model calls. If one chunk returns malformed output or raises a model error, the previous implementation discarded the successful chunks and reran the entire review with the fallback model.

This change caches the prepared chunks and successfully parsed results. Fallback attempts now retry only failed chunks, then merge successful chunk results in their original order. Once all fallback models are exhausted, any successful chunks are published with the failed count in the coverage note; incomplete reviews cannot resolve persistent findings.

Regression tests cover malformed output and model-call failures, including verification that successful chunks are not requested again.

Other comments

A review still fails when no chunk succeeds. Cached chunks retain the primary model's plan, so a smaller fallback can leave an oversized chunk uncovered.

Validation: 138 focused tests passed, including the real fallback-chain partial-publication and all-failed paths. Ruff and pre-commit passed.

AI Generation Disclosure

AI assistance was used for the collaborator-review fixes and regression tests.

Release Notes

  • pr_reviewer
    • Retry only failed large-review chunks on fallback models.

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

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Retry only failed chunks in large PR reviews

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Cache prepared review chunks and valid outputs across fallback model attempts.
• Retry only failed model or parsing chunks instead of repeating successful work.
• Merge recovered outputs in original chunk order while preserving unrecoverable failure behavior.
Diagram

graph TD
  A["Chunk cache"] --> B["Pending selector"] --> C["Model calls"] --> D{"Valid output?"} -- Yes --> E["Result cache"] --> F{"All complete?"} -- Yes --> H["Ordered merge"]
  D -- No --> G["Fallback model"] --> B
  F -- No --> G
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Per-chunk fallback routing
  • ➕ Each failed chunk could independently advance through fallback models.
  • ➕ Chunk recovery could complete within one orchestration pass.
  • ➖ Duplicates the existing centralized fallback-model policy.
  • ➖ Complicates concurrent scheduling, exception aggregation, and model configuration handling.

Recommendation: Keep the PR's indexed cache and reuse the existing fallback orchestration. It minimizes behavioral change, preserves configured model ordering and terminal failure semantics, and avoids repeating successful model calls while still producing a deterministic ordered merge.

Files changed (2) +51 / -23

Bug fix (1) +38 / -21
pr_reviewer.pyCache successful review chunks across fallback attempts +38/-21

Cache successful review chunks across fallback attempts

• Caches prepared chunk lists and valid parsed results on the reviewer. Subsequent fallback attempts select only unresolved indices, retain model and parsing failures for retry, and merge all successful results in original chunk order once complete.

pr_agent/tools/pr_reviewer.py

Tests (1) +13 / -2
test_review_large_diff_chunking.pyVerify selective retries for failed and malformed chunks +13/-2

Verify selective retries for failed and malformed chunks

• Extends large-diff regression coverage to run fallback attempts after model-call and YAML-validation failures. Assertions confirm successful chunks are not requested again and recovered results produce a complete merged review.

tests/unittest/test_review_large_diff_chunking.py

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

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

Copy link
Copy Markdown
Contributor

Code Review by Qodo

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

Grey Divider


Action required

1. Reserved output makes retries overflow 🐞 Bug ☼ Reliability
Description
_resize_pending_review_chunks, _include_newly_reviewable_files, and _get_prediction calculate
input capacity by subtracting the fixed 1,500-token OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD instead of
the active AI handler’s resolved output reserve. When configured completion limits, Claude
extended-thinking allowances, or OpenRouter reasoning reserves exceed that threshold, cached
fallback chunks pass resizing and preflight checks without enough context headroom, causing rejected
fallback requests or avoidable partial reviews even though reserve-aware packing would split them
further.
Code

pr_agent/tools/pr_reviewer.py[908]

+        budget = get_max_tokens(model) - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD - self.token_handler.prompt_tokens
Evidence
The initial multi-diff preparation path uses AttemptTokenBudget with get_output_token_reserve,
resolving the active handler’s model-specific reserve and subtracting it from available input
capacity. The retry resizing, omitted-file inclusion, and final pre-send calculations instead
subtract a fixed soft threshold, while the LiteLLM handler can resolve larger configured output or
reasoning reserves and sends that resolved limit as max_tokens, proving that the omitted headroom
directly affects whether the request fits the model context.

pr_agent/tools/pr_reviewer.py[797-804]
pr_agent/tools/pr_reviewer.py[849-860]
pr_agent/tools/pr_reviewer.py[904-944]
pr_agent/tools/pr_reviewer.py[999-1002]
pr_agent/algo/token_budget.py[57-91]
pr_agent/algo/ai_handlers/litellm_ai_handler.py[3579-3614]
pr_agent/algo/ai_handlers/litellm_ai_handler.py[3623-3640]
pr_agent/tools/pr_reviewer.py[904-930]
pr_agent/algo/pr_processing.py[757-776]
pr_agent/algo/ai_handlers/litellm_ai_handler.py[4087-4094]

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

## Issue description
Cached fallback retries calculate available input capacity using a fixed 1,500-token output allowance, while normal multi-diff preparation uses the AI handler’s model-specific resolved output reserve. This can admit chunks that do not fit alongside the configured completion limit, Claude extended-thinking allowance, or OpenRouter reasoning reserve.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[904-930]
- pr_agent/tools/pr_reviewer.py[938-961]
- pr_agent/tools/pr_reviewer.py[987-1002]
- pr_agent/algo/token_budget.py[17-91]
## Recommended Fix
Create or reuse one shared `AttemptTokenBudget` for the current model via `AttemptTokenBudget.for_attempt`, passing `ai_handler.get_output_token_reserve` when available and preserving the existing minimum reserve. Use that budget’s available-token calculation and model-bound tokenizer consistently for cached-chunk resizing, omitted-file inclusion, and the final pre-send chunk guard so these paths apply the same reserve policy as `get_pr_multi_diffs()`. Add coverage for resolved output reserves greater than 1,500 tokens.

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


2. Invalid chunk output reaches reviewers ✓ Resolved 🐞 Bug ≡ Correctness
Description
_prepare_chunked_prediction calls _validate_review_schema(data) but ignores its False return
value, then stores the schema-invalid response in _chunked_results as a successful chunk. When
another chunk causes a fallback attempt, that cached index is excluded from pending_indices, and
the merged prediction_data bypasses _prepare_pr_review's validation path.
Code

pr_agent/tools/pr_reviewer.py[R873-875]

+            try:
+                data = self._load_valid_review_yaml(prediction, source=f"review chunk {chunk_index + 1}")
+                self._validate_review_schema(data)
Evidence
The changed chunk loop ignores the validator's Boolean result before caching the output. The
validator returns False for Pydantic schema failures and missing enabled fields; cached indices
are omitted from retry requests, and merged cached data bypasses the later validation call.

pr_agent/tools/pr_reviewer.py[873-881]
pr_agent/tools/pr_reviewer.py[1014-1055]
pr_agent/tools/pr_reviewer.py[858-862]
pr_agent/tools/pr_reviewer.py[951-965]
pr_agent/tools/pr_reviewer.py[1070-1073]

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

The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
Issue description
`_prepare_chunked_prediction` treats a chunk as successful whenever YAML parsing succeeds, even when `_validate_review_schema()` returns `False`. This makes schema-invalid output persist in the cache, prevents the fallback model from retrying that chunk, and skips final validation because the merged output is assigned to `prediction_data`.
Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[873-881]
Recommended Fix
Check the Boolean result from `_validate_review_schema(data)` inside the existing per-chunk `try` block. When it is false, raise a `ValueError` (or otherwise route it through the existing `except` path) before writing to `chunk_results`, so the chunk remains pending and is retried by a fallback model.

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


3. Failed chunk reviews appear successful ✓ Resolved 🐞 Bug ≡ Correctness
Description
run catches the fallback-chain exception and continues whenever _merge_cached_review_chunks()
finds any cached result, bypassing the outer config.propagate_tool_errors check. When one chunk
still fails after every model, the command returns success to propagation-aware callers, so
command-line invocations can exit zero and GitHub can add its success reaction despite the exhausted
fallback chain.
Code

pr_agent/tools/pr_reviewer.py[R313-315]

+            except Exception:
+                if not self._merge_cached_review_chunks():
+                    raise
Evidence
The fallback helper raises after its final model fails, but the added inner handler suppresses that
exception whenever cached results exist. The reviewer's established outer handler is where
propagation is enforced, while the request orchestrator treats a normal return as success; GitHub
maps that result to its outcome reaction and the command-line entry point requires a false result
for a nonzero exit.

pr_agent/algo/pr_processing.py[494-512]
pr_agent/tools/pr_reviewer.py[439-444]
pr_agent/agent/pr_agent.py[298-320]
pr_agent/servers/github_app.py[148-155]
pr_agent/cli.py[218-223]
docs/docs/usage-guide/configuration_reference.md[113-115]

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

## Issue description
Fallback exhaustion is suppressed whenever cached chunks can be merged, even when `config.propagate_tool_errors` requires the tool to signal failure to its caller. Partial output should remain publishable, but propagation-aware callers must still receive the exhausted-fallback error.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[310-316]
- pr_agent/tools/pr_reviewer.py[439-460]
- tests/unittest/test_review_large_diff_chunking.py[268-338]
## Recommended Fix
Retain the fallback-exhaustion exception after merging cached chunks, allow the partial review and cleanup to complete, and then re-raise it when `config.propagate_tool_errors` is enabled without publishing a duplicate failure banner. Add coverage for both propagation settings, asserting that partial output is retained while the enabled case reports failure to the caller.

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


View high (5)
4. Larger fallbacks skip reviewable files ✓ Resolved 🐞 Bug ≡ Correctness
Description
_prepare_prediction enters the cached plan whenever _chunked_patches_diff_list exists, so
_prepare_chunked_prediction ignores a larger fallback's newly prepared full diff and retries only
indices from the primary plan. When the primary plan exhausted max_number_of_calls and left files
in _chunked_remaining_files_list, _merge_cached_review_chunks restores that old omission list
even though the fallback could have reviewed those files.
Code

pr_agent/tools/pr_reviewer.py[R798-799]

+        has_incomplete_chunk_plan = hasattr(self, "_chunked_patches_diff_list")
+        if chunking_enabled and (self.remaining_files_list or has_incomplete_chunk_plan):
Evidence
A fitting model returns the complete diff with no remaining files, while the new cache condition
still selects the primary plan. The packer documents that max_calls can leave files outside every
chunk, and the merge explicitly restores that primary omission list rather than the fallback's empty
list.

pr_agent/algo/pr_processing.py[104-113]
pr_agent/algo/pr_processing.py[215-282]
pr_agent/tools/pr_reviewer.py[786-802]
pr_agent/tools/pr_reviewer.py[819-845]
pr_agent/tools/pr_reviewer.py[875-889]

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 larger-context fallback can fit files omitted by the primary chunk plan, but the cached-plan path ignores the freshly prepared fallback diff and permanently preserves the primary omission list.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[786-889]
- pr_agent/algo/pr_processing.py[215-282]
- tests/unittest/test_review_large_diff_chunking.py[341-357]
## Recommended Fix
Retain successful cached chunk results, but when the current fallback can cover files in the cached remaining-files list, prepare additional work containing only those omitted files. Append that work to the pending chunk plan, update the cached remaining-files list from the fallback result, and test that already successful chunks are not requested again while newly coverable files are reviewed.

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


5. Valid chunk findings are withheld ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
_prepare_chunked_prediction() re-raises chunk_errors[0] whenever chunk_results is incomplete,
before assigning prediction_data, review_chunk_count, or review_failed_chunk_count. With one
malformed chunk and one valid chunk, execution stops before merge_review_chunks() and the
persistent-finding assertions in test_a_failed_chunk_blocks_persistent_finding_resolution, so
neither the coverage footer nor the finding-state safeguards can account for the partial review.
Code

pr_agent/tools/pr_reviewer.py[R861-864]

+        if len(chunk_results) < len(patches_diff_list):
+            if chunk_errors:
+                raise chunk_errors[0]
+            raise ValueError("No valid review output was produced for one or more chunks")
Evidence
Rules 3175559 and 3175560 require successfully parsed chunks to be merged into a partial review,
malformed chunks to contribute to the failed count, and incomplete coverage to be disclosed. The
production method instead records the chunk error and unconditionally re-raises it while results
remain incomplete, before merging or setting the counts consumed by the existing footer and
finding-state logic; the existing test demonstrates this with one successful chunk and one
RuntimeError, awaiting the method without handling the exception and therefore aborting before its
state assertions.

Retain valid review chunks when another chunk is malformed
Count malformed chunks as failed chunks and publish coverage warning
pr_agent/tools/pr_reviewer.py[850-864]
pr_agent/tools/pr_reviewer.py[1020-1028]
pr_agent/tools/pr_reviewer.py[841-864]
tests/unittest/test_review_large_diff_chunking.py[208-239]

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 malformed chunk causes `_prepare_chunked_prediction()` to raise even when other chunks parsed successfully, preventing the valid results, incomplete-coverage warning, and persistent-finding safeguards from being exercised. The existing persistent-state test supplies one failed and one successful chunk and expects the partial-result flow to complete, but now aborts before checking state.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[861-872]
- tests/unittest/test_review_large_diff_chunking.py[208-239]
## Recommended Fix
After processing all chunks, raise only when `chunk_results` is empty. Otherwise, merge valid results in index order, set `review_chunk_count` to the total chunk count, set `review_failed_chunk_count` to the number of missing chunks, and preserve the existing coverage-footer and finding-state behavior for partial reviews. Update or retain the persistent-state test so it exercises this completed fallback flow before inspecting state; if exception-based retry behavior is instead intended, explicitly assert the exception and replace the obsolete partial-result assertions with expectations matching that behavior.

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


6. Mixed failures lack required coverage ✓ Resolved 📎 Requirement gap ☼ Reliability
Description
test_a_malformed_chunk_is_retried_without_repeating_successful_chunks() uses only two chunks and
asserts only the eventual complete fallback result. It does not exercise a valid–malformed–valid
sequence or assert the intermediate failed count and coverage warning, so regressions that drop
either surrounding valid result remain undetected.
Code

tests/unittest/test_review_large_diff_chunking.py[R258-262]

+    reviewer._get_prediction.side_effect = [CHUNK_A]
+    await reviewer._prepare_prediction("fallback-model")
+
+    assert reviewer.prediction_data["review"]["score"] == "40"
+    assert [call.args[1] for call in reviewer._get_prediction.await_args_list] == ["chunk-a", "chunk-b", "chunk-a"]
Evidence
Rule 3175562 explicitly requires a valid–malformed–valid regression scenario with assertions for
both retained results, failed accounting, and the partial-review warning. The changed test supplies
only malformed and valid chunks, then verifies a fully recovered fallback result and request list.

Add regression coverage for mixed and total chunk failures
tests/unittest/test_review_large_diff_chunking.py[243-262]
tests/unittest/test_review_large_diff_chunking.py[351-358]

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 malformed-chunk regression test does not cover the required valid–malformed–valid sequence or verify failed-chunk accounting and the partial-review warning.
## Fix Focus Areas
- tests/unittest/test_review_large_diff_chunking.py[243-262]
## Recommended Fix
Use three chunk responses in valid–malformed–valid order and assert that both valid outputs are merged in their original order, the failed count is one, and the rendered review includes the incomplete-coverage warning. Keep separate coverage proving that an all-malformed attempt fails and allows fallback handling.

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


7. Larger fallbacks repeat the whole review ✓ Resolved 🐞 Bug ➹ Performance
Description
_prepare_prediction only reaches the new pending_indices cache logic when the current model's
remaining_files_list is non-empty. When a fallback has a larger token budget and fits the diff, it
bypasses _prepare_chunked_prediction and sends the entire pull request to the model again,
including every chunk that already succeeded.
Code

pr_agent/tools/pr_reviewer.py[R835-838]

+        chunk_results = getattr(self, "_chunked_results", {})
+        pending_indices = [index for index in range(len(patches_diff_list)) if index not in chunk_results]
predictions = await asyncio.gather(
-            *[self._get_prediction(model, patches_diff) for patches_diff in patches_diff_list],
+            *[self._get_prediction(model, patches_diff_list[index]) for index in pending_indices],
Evidence
Diff splitting is explicitly based on each model's token limit, while fallback orchestration calls
_prepare_prediction again with the next model. The current-attempt truncation guard precedes and
can bypass the newly added cache lookup, after which the single-call path reviews the whole diff.

pr_agent/tools/pr_reviewer.py[781-800]
pr_agent/tools/pr_reviewer.py[812-839]
pr_agent/algo/pr_processing.py[549-569]
pr_agent/algo/pr_processing.py[480-512]

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

## Issue description
Cached successful chunks are consulted only after the current model reports a truncated diff. A larger fallback model can fit the full diff, bypass the cache, and repeat all successful model work.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[757-839]
## Recommended Fix
When an incomplete chunk cache exists, resume its pending indices before deciding between the current model's chunked and single-call paths. Do not allow a newly fitting whole diff to bypass pending cached work.

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


8. Smaller fallback models reject chunks ✓ Resolved 🐞 Bug ☼ Reliability
Description
_prepare_chunked_prediction reuses _chunked_patches_diff_list without checking whether its
chunks fit the current fallback model's tokenizer and context limit. When the primary model creates
a chunk larger than a fallback accepts, the pending request exceeds that fallback's budget and can
exhaust the fallback chain even though regenerating its chunks would allow the review to complete.
Code

pr_agent/tools/pr_reviewer.py[R810-812]

+        patches_diff_list = getattr(self, "_chunked_patches_diff_list", None)
+        remaining_files_list = getattr(self, "_chunked_remaining_files_list", None)
+        if patches_diff_list is None:
Evidence
Chunk packing compares each patch and accumulated chunk against get_max_tokens(model), proving
boundaries are model-specific. The fallback loop accepts an arbitrary configured sequence of models,
while the new cache bypasses chunk regeneration after the first model and sends cached pending
chunks directly to later models.

pr_agent/tools/pr_reviewer.py[810-836]
pr_agent/algo/pr_processing.py[215-268]
pr_agent/algo/pr_processing.py[480-512]
pr_agent/algo/pr_processing.py[518-531]

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

## Issue description
Cached chunks are generated using the primary model's token handler and context limit, then reused for fallback models without compatibility validation. A smaller fallback can therefore receive an oversized pending chunk and fail unnecessarily.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[810-856]
- pr_agent/algo/pr_processing.py[215-282]
- tests/unittest/test_review_large_diff_chunking.py[191-262]
## Recommended Fix
Associate the cached chunk plan with the model and token-budget assumptions used to create it. Before reusing it for a fallback, verify every pending chunk against the fallback model's token handler and maximum-token limit; regenerate or split incompatible pending chunks while preserving successful results and their merge order. Add a regression test where the fallback has a smaller context limit than the primary model.

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



Remediation recommended

9. A schema comment uses narrative style 📘 Rule violation ⚙ Maintainability
Description
test_invalid_chunk_emits_one_schema_warning_before_rendering adds `# schema validation stays
warn-only...`, which narrates current behavior instead of instructing the reader. When validation
behavior changes, the comment does not clearly direct maintainers to preserve the merge-versus-retry
invariant.
Code

tests/unittest/test_review_large_diff_chunking.py[563]

+    # schema validation stays warn-only (#3372): the chunk is merged, not retried
Evidence
Compliance rule 2694688 requires newly added behavior comments to use imperative phrasing. The added
comment at line 563 describes the current behavior with schema validation stays rather than
directing maintainers to preserve it.

Rule 2694688: Docstrings and comments must use imperative phrasing
tests/unittest/test_review_large_diff_chunking.py[563-563]

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 schema-validation comment uses descriptive phrasing rather than the required imperative style.
## Fix Focus Areas
- tests/unittest/test_review_large_diff_chunking.py[563-563]
## Recommended Fix
Rewrite the comment as an instruction, such as `# Keep schema validation warn-only: merge the chunk instead of retrying it.`

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


10. A retry comment breaks imperative style 📘 Rule violation ⚙ Maintainability
Description
_prepare_prediction adds the declarative comment `Otherwise the single-call path would bypass
cached successful chunks.` instead of expressing the guidance as an instruction. A later maintainer
encounters a different voice from the command-style comments required for this fallback-resume
branch.
Code

pr_agent/tools/pr_reviewer.py[815]

+        # Otherwise the single-call path would bypass cached successful chunks.
Evidence
Compliance rule 2694688 requires newly added behavioral comments to use imperative phrasing, while
the added line begins with the declarative Otherwise the single-call path would bypass....

Rule 2694688: Docstrings and comments must use imperative phrasing
pr_agent/tools/pr_reviewer.py[815-815]

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 fallback-resume comment describes a consequence declaratively rather than using the required imperative phrasing.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[815-815]
## Recommended Fix
Rewrite the comment as an imperative instruction, such as `Avoid bypassing cached successful chunks through the single-call path.`

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


11. A summary can hide an unreviewed file 🐞 Bug ≡ Correctness
Description
_include_newly_reviewable_files splits on every line shaped like a file header, including
identical text embedded verbatim in an AI-generated file summary, and marks the first matching name
as included. When an earlier summary contains ## File: 'other.py' while other.py is pending and
the fallback chunk succeeds, its metadata fragment is appended instead of the real diff and the
filename is removed from remaining_files_list, so the review claims full coverage without
examining that file.
Code

pr_agent/tools/pr_reviewer.py[R947-950]

+        for section in re.split(r"(?=^## File: ')", self.patches_diff or "", flags=re.MULTILINE):
+            match = re.match(r"## File: '(.*)'\n", section)
+            if not match or match[1] not in remaining or match[1] in included:
+                continue
Evidence
AI summaries are inserted verbatim directly after canonical file headers. The new recovery parser
splits on the same unrestricted header syntax, marks the first matching section included, skips
later sections with that name, and removes the name from omission tracking.

pr_agent/algo/pr_processing.py[871-879]
pr_agent/tools/pr_reviewer.py[947-962]
pr_agent/algo/utils.py[2019-2031]

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 omitted-file recovery parser treats reserved file-header text inside AI metadata as a real top-level file boundary. It can therefore append metadata under a pending filename, skip the actual file block, and remove that filename from coverage tracking.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[938-962]
- pr_agent/algo/pr_processing.py[871-879]
## Recommended Fix
Prevent metadata from emitting unescaped top-level file markers or replace the regex split with structured file-block data. Only remove a filename from the remaining list after its canonical diff block has actually been appended, and add a regression test where an earlier summary contains the exact header of a pending file.

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


View medium (7)
12. Fallbacks can spend calls on whitespace 🐞 Bug ☼ Reliability
Description
_resize_pending_review_chunks keeps the leading whitespace returned before the first file marker
as its own part when adding that prefix would push the first file over budget. Because generated
chunks retain leading newlines whenever files remain unpacked, a boundary-sized fallback can send a
blank model call or reject an otherwise feasible split against max_number_of_calls, leaving that
code uncovered.
Code

pr_agent/tools/pr_reviewer.py[R922-925]

+                if current and self.token_handler.count_tokens(current + section) > budget:
+                    parts.append(current)
+                    current = ""
+                current += section
Evidence
Line-numbered file patches are generated with two leading newlines, and the multi-diff packer strips
them only in the fully packed final chunk. The new splitter retains the pre-marker result and can
add it to parts, after which every accepted part becomes a model request.

pr_agent/algo/git_patch_processing.py[347-353]
pr_agent/algo/pr_processing.py[410-427]
pr_agent/tools/pr_reviewer.py[919-934]
pr_agent/tools/pr_reviewer.py[870-874]

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

## Issue description
Regex splitting can return the generated leading newlines as a separate section. Near the fallback token boundary, that whitespace becomes a standalone chunk and either consumes a model call or prevents an otherwise valid split from fitting the configured call limit.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[919-931]
- tests/unittest/test_review_large_diff_chunking.py[474-502]
## Recommended Fix
Discard empty or whitespace-only sections before assembling split parts while preserving separators within actual file sections. Add a regression test with a leading-newline chunk, a fallback budget at the first-file boundary, and a call limit that permits the real file split but not an extra blank part.

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


13. Automation sees partial reviews as full 🐞 Bug ≡ Correctness
Description
run continues into _prepare_pr_review after _merge_cached_review_chunks() recovers an
incomplete cache, but the GitHub Action output, provider-neutral structured output, and external
sink payload receive only the merged review data. When fallback exhaustion leaves failed chunks,
only the later Markdown footer carries review_failed_chunk_count, so consumers of JSON, action
outputs, or webhooks receive no completeness indicator.
Code

pr_agent/tools/pr_reviewer.py[R318-320]

+                if not self._merge_cached_review_chunks():
+                    raise
+                partial_review_error = error
Evidence
The new recovery branch explicitly resumes publication with an incomplete chunk cache.
_prepare_pr_review emits the unannotated merged data to three machine-readable channels before
adding failed coverage only to Markdown, while the documentation promises a failed-chunk coverage
warning for partial publication.

pr_agent/tools/pr_reviewer.py[314-325]
pr_agent/tools/pr_reviewer.py[1071-1100]
pr_agent/tools/pr_reviewer.py[1123-1139]
pr_agent/tools/pr_reviewer.py[1191-1195]
docs/docs/tools/review.md[281-286]

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

## Issue description
Fallback exhaustion now publishes cached successful chunks, but machine-readable review channels receive no failed-chunk or omitted-file coverage metadata. Automation consuming structured output can therefore treat an incomplete review as complete.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[318-320]
- pr_agent/tools/pr_reviewer.py[1071-1100]
- pr_agent/tools/pr_reviewer.py[1191-1195]
## Recommended Fix
Add provider-neutral coverage metadata containing the total chunk count, failed chunk count, and remaining files to every machine-readable review payload before writing GitHub Action, structured-provider, or external-sink output. Preserve the existing review mapping shape where compatibility requires it by adding the metadata as a documented sibling field.

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


14. Partial reviews erase security labels 🐞 Bug ≡ Correctness
Description
run passes cached partial results into _prepare_pr_review, which unconditionally calls
set_review_labels with the incomplete merged data. When successful chunks report no security
concern but another chunk remains failed, label reconciliation filters out an existing `Possible
security concern` label even though the uncovered code was not reviewed.
Code

pr_agent/tools/pr_reviewer.py[R318-320]

+                if not self._merge_cached_review_chunks():
+                    raise
+                partial_review_error = error
Evidence
The added exception recovery turns an exhausted fallback chain into a normal rendering path whenever
any cached chunk succeeded. Rendering always invokes set_review_labels, and that method removes
all existing review-security labels before adding one back only when the partial merged response
itself reports a concern; the documentation confirms these results may have failed chunks.

pr_agent/tools/pr_reviewer.py[314-325]
pr_agent/tools/pr_reviewer.py[1191-1198]
pr_agent/tools/pr_reviewer.py[1439-1478]
docs/docs/tools/review.md[281-285]

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 partial-publication path runs normal label reconciliation using only successful chunks. This can remove an existing security or effort label despite failed chunks leaving the current review incomplete.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[318-320]
- pr_agent/tools/pr_reviewer.py[1197-1198]
- pr_agent/tools/pr_reviewer.py[1430-1478]
## Recommended Fix
Skip authoritative review-label reconciliation whenever `review_failed_chunk_count` is nonzero. Preserve the existing review labels until a complete review can derive replacements from all covered chunks.

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


15. Run details exceed the width limit 📘 Rule violation ⚙ Maintainability
Description
show_run_details() puts the complete multi-model label and conditional fallback suffix on physical
line 1887, exceeding the configured 120-character limit. A later edit to the model list or suffix
must work within an already oversized expression, making changes harder to scan and review.
Code

pr_agent/algo/utils.py[1887]

+        lines = [f"- Models: {', '.join(details.models_used)}{' (includes fallback)' if details.fallback_used else ''}"]
Evidence
PR Compliance ID 2694655 requires every modified Python line to remain within 120 characters. The
repository configures Ruff with line-length = 120, while the newly added multi-model run-details
assignment exceeds that limit.

Rule 2694655: Limit Python source lines to 120 characters as configured
pr_agent/algo/utils.py[1887-1887]
pyproject.toml[146-147]

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 multi-model run-details expression exceeds the configured 120-character Python line limit.
## Fix Focus Areas
- pr_agent/algo/utils.py[1887-1887]
## Recommended Fix
Wrap the list assignment and its f-string across multiple physical lines using parentheses while preserving the rendered text.

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


16. Partial reviews hide fallback usage 🐞 Bug ◔ Observability
Description
_merge_cached_review_chunks infers fallback participation by comparing each cached model name with
_chunked_primary_model, even though fallback attempts may use the same model through a different
deployment. When such a fallback contributes chunks but the chain ultimately exhausts, the published
run details identify only the primary model and omit the fallback marker.
Code

pr_agent/tools/pr_reviewer.py[R970-971]

+            for model in models:
+                record_model_used(model, is_fallback=model != self._chunked_primary_model)
Evidence
The retry chain distinguishes attempts by position and pairs model names with separate deployment
IDs, so a fallback can legitimately share the primary model name. The new merge logic discards that
positional identity and tests only model-name inequality, while the renderer relies on
fallback_used to disclose fallback participation.

pr_agent/algo/pr_processing.py[480-511]
pr_agent/tools/pr_reviewer.py[966-971]
pr_agent/algo/utils.py[1886-1889]

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

## Issue description
Partial chunk reviews infer fallback participation from model-name inequality, which fails when primary and fallback deployments use the same model identifier.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[864-882]
- pr_agent/tools/pr_reviewer.py[966-971]
- pr_agent/algo/pr_processing.py[480-515]
## Recommended Fix
Track whether each chunk result came from a fallback attempt independently of its model string, store that flag with the cached result, and mark run details as using a fallback whenever any merged chunk carries the flag.

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


17. Users lack partial retry guidance ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
docs/docs/tools/review.md describes chunking only as reviewing every chunk and merging all
answers, without documenting failed-chunk retries or publication of cached successes. When fallback
models are exhausted in run, users must infer why a partial review is still published and how that
behavior relates to the failed-chunk coverage notice.
Code

pr_agent/tools/pr_reviewer.py[R313-316]

+            except Exception:
+                if not self._merge_cached_review_chunks():
+                    raise
+                get_logger().warning("Fallback models exhausted; publishing successful review chunks")
Evidence
Rule 2694680 requires documentation to reflect changed user-facing workflows and outputs. The
changed exception path publishes cached review chunks after fallback exhaustion, while the existing
large-PR documentation describes only splitting and merging all chunk answers and does not cover
failed-chunk retries or partial publication.

Rule 2694680: Update docs when user-facing behavior changes
pr_agent/tools/pr_reviewer.py[313-316]
docs/docs/tools/review.md[263-292]

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

## Issue description
Large-PR reviews now retry only failed chunks and publish cached successful chunks after fallback exhaustion, but the review documentation does not describe this user-facing behavior.
## Fix Focus Areas
- docs/docs/tools/review.md[263-292]
## Recommended Fix
Extend the large-PR chunking section to explain that successful chunks are retained, fallback models retry failed chunks, exhausted retries can produce a partial review with a failed-chunk coverage warning, and incomplete reviews cannot resolve absent persistent findings.

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


18. A merge comment is not imperative ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
_merge_cached_review_chunks changes the raw-text comment to the passive description `The raw text
is kept...` instead of an imperative instruction. When the merge implementation changes, a later
maintainer reads it as an observation of current behavior rather than guidance about which
representation to preserve.
Code

pr_agent/tools/pr_reviewer.py[881]

+        # The raw text is kept for logging only; the merged verdict is in self.prediction_data.
Evidence
Compliance rule 2694688 requires modified behavior comments to use imperative phrasing. The changed
comment at line 881 instead uses the passive constructions is kept and is in.

Rule 2694688: Docstrings and comments must use imperative phrasing
pr_agent/tools/pr_reviewer.py[881-881]

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 modified raw-text comment uses passive descriptive prose instead of the required imperative phrasing.
## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[881-881]
## Recommended Fix
Rewrite the comment as an imperative instruction, such as `Keep raw text for logging only; use the merged verdict from self.prediction_data.`

ⓘ 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 group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +810 to +812
patches_diff_list = getattr(self, "_chunked_patches_diff_list", None)
remaining_files_list = getattr(self, "_chunked_remaining_files_list", None)
if patches_diff_list is None:

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. Smaller fallback models reject chunks 🐞 Bug ☼ Reliability

_prepare_chunked_prediction reuses _chunked_patches_diff_list without checking whether its
chunks fit the current fallback model's tokenizer and context limit. When the primary model creates
a chunk larger than a fallback accepts, the pending request exceeds that fallback's budget and can
exhaust the fallback chain even though regenerating its chunks would allow the review to complete.
Agent Prompt
## Issue description
Cached chunks are generated using the primary model's token handler and context limit, then reused for fallback models without compatibility validation. A smaller fallback can therefore receive an oversized pending chunk and fail unnecessarily.

## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[810-856]
- pr_agent/algo/pr_processing.py[215-282]
- tests/unittest/test_review_large_diff_chunking.py[191-262]

## Recommended Fix
Associate the cached chunk plan with the model and token-budget assumptions used to create it. Before reusing it for a fallback, verify every pending chunk against the fallback model's token handler and maximum-token limit; regenerate or split incompatible pending chunks while preserving successful results and their merge order. Add a regression test where the fallback has a smaller context limit than the primary model.

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

Comment thread pr_agent/tools/pr_reviewer.py
Comment thread tests/unittest/test_review_large_diff_chunking.py Outdated
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 d3f2d00

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

Copy link
Copy Markdown
Contributor

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

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

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 0263ccf

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

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 0d959cf

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

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 01eb781

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

Copy link
Copy Markdown
Contributor

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

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

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 95e1e00

@GentleYo

Copy link
Copy Markdown

Preparing review...

@GentleYo

Copy link
Copy Markdown

Failed to review PR

1 similar comment
@GentleYo

Copy link
Copy Markdown

Failed to review PR

@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 turning the requested shape round within the hour of the decline.

Not yet. The retry is right: a valid chunk is never resent to the fallback. But the terminal state moved with it. On main a chunk that fails with no model left still publishes the rest with the failed count in the footer; here the review fails, review_failed_chunk_count is only ever 0, and the #3229 finding-state guard is dead. Keep the raise while a later model can retry, and on the last one publish what succeeded with the failed count; the simplest place is run() after the chain is exhausted, merging the cache. A test pinning the partial result through retry_with_fallback_models goes with it.

Qodo's open point stands: main re-chunks per model, this reuses the primary's plan. With the partial path back that costs a chunk, not the review.

Comment thread pr_agent/tools/pr_reviewer.py Fixed
Comment on lines +798 to +799
has_incomplete_chunk_plan = hasattr(self, "_chunked_patches_diff_list")
if chunking_enabled and (self.remaining_files_list or has_incomplete_chunk_plan):

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. Larger fallbacks skip reviewable files 🐞 Bug ≡ Correctness

_prepare_prediction enters the cached plan whenever _chunked_patches_diff_list exists, so
_prepare_chunked_prediction ignores a larger fallback's newly prepared full diff and retries only
indices from the primary plan. When the primary plan exhausted max_number_of_calls and left files
in _chunked_remaining_files_list, _merge_cached_review_chunks restores that old omission list
even though the fallback could have reviewed those files.
Agent Prompt
## Issue description
A larger-context fallback can fit files omitted by the primary chunk plan, but the cached-plan path ignores the freshly prepared fallback diff and permanently preserves the primary omission list.

## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[786-889]
- pr_agent/algo/pr_processing.py[215-282]
- tests/unittest/test_review_large_diff_chunking.py[341-357]

## Recommended Fix
Retain successful cached chunk results, but when the current fallback can cover files in the cached remaining-files list, prepare additional work containing only those omitted files. Append that work to the pending chunk plan, update the cached remaining-files list from the fallback result, and test that already successful chunks are not requested again while newly coverable files are reviewed.

ⓘ 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 4ed3c3b

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

qodo-free-for-open-source-projects Bot commented Sep 16, 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

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 fce4e9f

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 c3c17c1

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

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 639d554

@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 bringing the partial path back through run() and pinning it on the real fallback chain; only the failed chunk is resent now, exhaustion publishes what succeeded with the failed count, and cached results keep their chunk when a smaller fallback splits pending work.

One thing, from the last commit. A chunk that parses but fails the schema is now retried and, on the last model, dropped. #3372 set that check to warn-only yesterday and the single-call path still only warns, so a chunk answering relevant_tests: no loses its findings after a wasted fallback call, where main merges them. The inlines restore the warn-only call and main's test.

Comment on lines +873 to +882
try:
data = self._load_valid_review_yaml(prediction, source=f"review chunk {chunk_index + 1}")
if not self._validate_review_schema(data):
raise ValueError(f"review chunk {chunk_index + 1} failed schema validation")
except Exception as error:
chunk_errors.append(error)
get_logger().warning(f"Failed to parse review chunk {chunk_index + 1}; retrying it with fallback",
artifact={"error": error})
continue
chunk_results[chunk_index] = (prediction, data, model)

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.

Schema failures stayed warn-only in #3372 and still are in _prepare_pr_review; raising here drops a chunk whose YAML reads no as a boolean. The parse stays the retry trigger.

Suggested change
try:
data = self._load_valid_review_yaml(prediction, source=f"review chunk {chunk_index + 1}")
if not self._validate_review_schema(data):
raise ValueError(f"review chunk {chunk_index + 1} failed schema validation")
except Exception as error:
chunk_errors.append(error)
get_logger().warning(f"Failed to parse review chunk {chunk_index + 1}; retrying it with fallback",
artifact={"error": error})
continue
chunk_results[chunk_index] = (prediction, data, model)
try:
data = self._load_valid_review_yaml(prediction, source=f"review chunk {chunk_index + 1}")
except Exception as error:
chunk_errors.append(error)
get_logger().warning(f"Failed to parse review chunk {chunk_index + 1}; retrying it with fallback",
artifact={"error": error})
continue
self._validate_review_schema(data)
chunk_results[chunk_index] = (prediction, data, model)


@pytest.mark.asyncio
async def test_invalid_chunk_emits_one_schema_warning_before_rendering(chunking_enabled):
async def test_invalid_chunk_is_retried_after_a_single_schema_warning(chunking_enabled):

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.

Names what the test now pins.

Suggested change
async def test_invalid_chunk_is_retried_after_a_single_schema_warning(chunking_enabled):
async def test_invalid_chunk_emits_one_schema_warning_before_rendering(chunking_enabled):

Comment on lines +551 to +569
pytest.raises(ValueError, match="failed schema validation"),
):
await reviewer._prepare_prediction("model")
reviewer._prepare_pr_review()

warnings = get_logger.return_value.warning.call_args_list
schema_warnings = [call for call in warnings if call.args == ("Review output failed schema validation",)]
assert len(schema_warnings) == 1
assert schema_warnings[0].kwargs["artifact"] == {"field": "review.score", "value": 101}
assert reviewer.prediction_data is None

reviewer._get_prediction.side_effect = [CHUNK_A]
await reviewer._prepare_chunked_prediction("model")
reviewer._prepare_pr_review()

assert reviewer.review_chunk_count == 2
assert reviewer.review_failed_chunk_count == 0
assert [call.args[1] for call in reviewer._get_prediction.await_args_list] == [
"chunk-a", "chunk-b", "chunk-a",
]

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.

Main's contract: one warning, no retry, chunk merged.

Suggested change
pytest.raises(ValueError, match="failed schema validation"),
):
await reviewer._prepare_prediction("model")
reviewer._prepare_pr_review()
warnings = get_logger.return_value.warning.call_args_list
schema_warnings = [call for call in warnings if call.args == ("Review output failed schema validation",)]
assert len(schema_warnings) == 1
assert schema_warnings[0].kwargs["artifact"] == {"field": "review.score", "value": 101}
assert reviewer.prediction_data is None
reviewer._get_prediction.side_effect = [CHUNK_A]
await reviewer._prepare_chunked_prediction("model")
reviewer._prepare_pr_review()
assert reviewer.review_chunk_count == 2
assert reviewer.review_failed_chunk_count == 0
assert [call.args[1] for call in reviewer._get_prediction.await_args_list] == [
"chunk-a", "chunk-b", "chunk-a",
]
):
await reviewer._prepare_prediction("model")
reviewer._prepare_pr_review()
warnings = get_logger.return_value.warning.call_args_list
schema_warnings = [call for call in warnings if call.args == ("Review output failed schema validation",)]
assert len(schema_warnings) == 1
assert schema_warnings[0].kwargs["artifact"] == {"field": "review.score", "value": 101}
# schema validation stays warn-only (#3372): the chunk is merged, not retried
assert reviewer._get_prediction.await_count == 2
assert reviewer.review_chunk_count == 2
assert reviewer.review_failed_chunk_count == 0
assert reviewer.prediction_data["review"]["score"] == 40

Resolves the pr_reviewer.py conflict with The-PR-Agent#3389 by keeping the cached-chunk path and forwarding output_token_reserve inside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpThzLDt7pgLathoucBkcL
@IsmaelMartinez

Copy link
Copy Markdown
Collaborator

#3389 landed a few minutes after the review above and this branch conflicted with it in the multi_diff_kwargs block, so I merged main into your branch (17e12ac, a fast-forward on top of 639d554): your cached-chunk path is kept and the output_token_reserve forwarding from #3389 sits inside it. Nothing else changed; the three inline suggestions are still yours to apply. Full suite 8568 green on the result.

"""Split oversized pending chunks at file boundaries while preserving result order."""
chunks = self._chunked_patches_diff_list
results = getattr(self, "_chunked_results", {})
budget = get_max_tokens(model) - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD - self.token_handler.prompt_tokens

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. Reserved output makes retries overflow 🐞 Bug ☼ Reliability

_resize_pending_review_chunks, _include_newly_reviewable_files, and _get_prediction calculate
input capacity by subtracting the fixed 1,500-token OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD instead of
the active AI handler’s resolved output reserve. When configured completion limits, Claude
extended-thinking allowances, or OpenRouter reasoning reserves exceed that threshold, cached
fallback chunks pass resizing and preflight checks without enough context headroom, causing rejected
fallback requests or avoidable partial reviews even though reserve-aware packing would split them
further.
Agent Prompt
## Issue description
Cached fallback retries calculate available input capacity using a fixed 1,500-token output allowance, while normal multi-diff preparation uses the AI handler’s model-specific resolved output reserve. This can admit chunks that do not fit alongside the configured completion limit, Claude extended-thinking allowance, or OpenRouter reasoning reserve.

## Fix Focus Areas
- pr_agent/tools/pr_reviewer.py[904-930]
- pr_agent/tools/pr_reviewer.py[938-961]
- pr_agent/tools/pr_reviewer.py[987-1002]
- pr_agent/algo/token_budget.py[17-91]

## Recommended Fix
Create or reuse one shared `AttemptTokenBudget` for the current model via `AttemptTokenBudget.for_attempt`, passing `ai_handler.get_output_token_reserve` when available and preserving the existing minimum reserve. Use that budget’s available-token calculation and model-bound tokenizer consistently for cached-chunk resizing, omitted-file inclusion, and the final pre-send chunk guard so these paths apply the same reserve policy as `get_pr_multi_diffs()`. Add coverage for resolved output reserves greater than 1,500 tokens.

ⓘ 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 17e12ac

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

Copy link
Copy Markdown
Contributor

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

@utsab345

Copy link
Copy Markdown
Contributor Author

@IsmaelMartinez done — schema validation is warn-only again (parse failures still retry with fallback), and the test was reverted to test_invalid_chunk_emits_one_schema_warning_before_rendering with main's contract assertions. Pushed in dd42732.

@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 applying the three inlines exactly and keeping the parse failure as the only retry trigger.

Approving. A chunk answering relevant_tests: no now merges with one warning and no retry, as on main; a parse failure resends only that chunk; exhaustion still publishes what succeeded with the failed count. Suite green on today's main, coverage holds.

Qodo's reserve point (a fixed 1500 against the handler's reserve in the resize and pre-send checks) is real but narrow: fallback path only, reserve above 1500, and it degrades to a labelled partial review. I will file it as a follow-up on top of #3395 rather than hold this.

@IsmaelMartinez
IsmaelMartinez merged commit f957b66 into The-PR-Agent:main Sep 16, 2026
6 checks passed
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: malformed chunk discards successful chunks in large-PR chunking

4 participants