Skip to content

fix(improve): clean up progress comment on cancellation - #2850

Merged
IsmaelMartinez merged 6 commits into
The-PR-Agent:mainfrom
junnhwan:fix/internal-core-pipeline-audit-20260828
Aug 29, 2026
Merged

fix(improve): clean up progress comment on cancellation#2850
IsmaelMartinez merged 6 commits into
The-PR-Agent:mainfrom
junnhwan:fix/internal-core-pipeline-audit-20260828

Conversation

@junnhwan

Copy link
Copy Markdown
Contributor

Fixes #2849

What

Clean up the /improve progress comment when the command task is cancelled
after progress publication.

Why

asyncio.CancelledError bypasses the existing except Exception cleanup path.
The cancellation correctly propagates, but the provider-owned progress comment
is left without a deletion attempt and can remain visible as stale status.

Implementation

  • Handle asyncio.CancelledError before the ordinary exception handler.
  • Attempt remove_comment() only when this run has a progress handle.
  • Log cleanup failures and re-raise the original cancellation.
  • Add fake-provider coverage for GFM and temporary progress comments and for a
    cleanup failure that must not mask cancellation.

Tests

  • pytest -q tests/unittest/test_pr_code_suggestions_lifecycle.py tests/unittest/test_pr_code_suggestions_core.py — 71 passed
  • pytest -q tests/unittest — 2576 passed, 1 skipped, 1 xfailed, 89 warnings
  • python -m py_compile pr_agent/tools/pr_code_suggestions.py tests/unittest/test_pr_code_suggestions_lifecycle.py
  • git diff --check

No real model, token, external provider, or production deployment was used.

Signed-off-by: hwan <3373484735@qq.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Clean up /improve progress comments on cancellation

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Removes /improve progress comments when asynchronous suggestion generation is cancelled.
• Preserves cancellation when provider cleanup fails, while logging the cleanup error.
• Tests GFM, temporary-comment, and failed-deletion lifecycle paths.
Diagram

sequenceDiagram
    actor Caller
    participant Improve as /improve
    participant Provider as Git Provider
    participant Model as Model Retry
    participant Logger
    Caller->>Improve: Start run
    Improve->>Provider: Publish progress
    Improve->>Model: Generate suggestions
    Model--xImprove: CancelledError
    Improve->>Provider: Remove progress
    alt Cleanup fails
        Provider--xImprove: Cleanup error
        Improve->>Logger: Log failure
    end
    Improve--xCaller: Re-raise cancellation
Loading
High-Level Assessment

The cancellation-specific handler is the best fit because it preserves Python cancellation semantics while limiting cleanup to runs that actually own a progress handle. A broad finally cleanup was considered but would overlap existing success and ordinary-error lifecycle handling, increasing the risk of duplicate deletion or removing comments already repurposed as final output.

Files changed (2) +110 / -0

Bug fix (1) +10 / -0
pr_code_suggestions.pyRemove progress comments during improve-task cancellation +10/-0

Remove progress comments during improve-task cancellation

• Adds explicit 'asyncio.CancelledError' handling around '/improve' execution. The handler performs best-effort removal of the current run's progress comment, logs provider cleanup failures, and re-raises the original cancellation.

pr_agent/tools/pr_code_suggestions.py

Tests (1) +100 / -0
test_pr_code_suggestions_lifecycle.pyCover cancellation progress-comment lifecycle +100/-0

Cover cancellation progress-comment lifecycle

• Adds isolated fake-provider tests for GFM and temporary progress comments. It also verifies that deletion failures do not mask 'asyncio.CancelledError' and restores mutated settings after each case.

tests/unittest/test_pr_code_suggestions_lifecycle.py

@qodo-code-review

qodo-code-review Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📜 Skill insights (0)

Grey Divider


Action required

1. Check-run path leaves progress comment ✓ Resolved 🐞 Bug ≡ Correctness
Description
When publish_persistent_comment_with_history() successfully publishes a check run, it returns
None without cleaning the supplied progress comment; the new is not None guard therefore leaves
self.progress_response set. A normal run then exits without deleting that comment, so the
temporary progress status remains visible indefinitely (and cancellation can later treat it as
still-owned progress).
Code

pr_agent/tools/pr_code_suggestions.py[R249-250]

+                        if published_comment is not None:
+                            self.progress_response = None
Relevance

●●● Strong

Recent accepted precedents require clearing or cleaning progress handles after persistent
publication; this is the same stale-comment bug.

PR-#2404
PR-#2833

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed guard clears ownership only for a non-None return. In the same branch's callee, a
successful _publish_check_run() causes an immediate bare return, while the caller's normal
persistent path has no subsequent progress cleanup; only the cancellation handler later acts on the
retained handle.

pr_agent/tools/pr_code_suggestions.py[249-250]
pr_agent/tools/pr_code_suggestions.py[379-381]
pr_agent/tools/pr_code_suggestions.py[262-269]
pr_agent/tools/pr_code_suggestions.py[274-287]

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

## Issue description
Successful check-run publication returns `None`, so the new conditional does not clear or otherwise clean the provider-owned progress comment. This leaves the temporary progress comment visible after a normal persistent run.

## Issue Context
`publish_persistent_comment_with_history()` returns early when `_publish_check_run()` succeeds, before its comment-update/cleanup logic. The caller must handle that success path explicitly without confusing it with a failed publication.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[249-250]
- pr_agent/tools/pr_code_suggestions.py[379-381]
- pr_agent/tools/pr_code_suggestions.py[420-433]

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


2. Persistent summary remains cleanup-owned ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new ownership reset only runs in the non-persistent branch, while the persistent fallback can
edit the same progress_response into the final summary without clearing self.progress_response.
If dual_publishing() is then cancelled, the cancellation handler treats that final summary as
progress, overwrites it with the cancellation message, and removes it.
Code

pr_agent/tools/pr_code_suggestions.py[256]

+                            self.progress_response = None
Relevance

●●● Strong

Recent PR #2404 accepted protecting persistent publication from progress cleanup, directly matching
this ownership regression.

PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
run() passes the tracked progress handle into persistent publishing, but the newly added reset
exists only in the else branch for non-persistent publication. The persistent helper's fallback
explicitly converts that progress comment into the new final persistent comment and returns it;
afterward run() awaits dual publishing, and its cancellation handler cleans any still-tracked
handle. The new regression test forces persistent_comment = False, so it does not exercise this
default-enabled persistent path.

pr_agent/tools/pr_code_suggestions.py[236-262]
pr_agent/tools/pr_code_suggestions.py[547-557]
pr_agent/tools/pr_code_suggestions.py[272-291]
tests/unittest/test_pr_code_suggestions_lifecycle.py[98-110]
pr_agent/settings/configuration.toml[169-179]
PR-#2404

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 progress handle is relinquished only after non-persistent summary publication. When persistent publication creates its first summary by editing that same progress comment, cancellation during subsequent dual publishing still overwrites and deletes the completed summary.

## Issue Context
Track whether `publish_persistent_comment_with_history()` promoted `progress_response` into the persistent summary, and clear or otherwise transfer that handle before awaiting dual publishing. Preserve cleanup behavior for a progress note that remains separate from an already-existing persistent thread, and add coverage for persistent mode with no prior summary comment.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[236-262]
- pr_agent/tools/pr_code_suggestions.py[547-557]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[78-114]

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


3. Cancellation deletes final summary ✓ Resolved 🐞 Bug ≡ Correctness
Description
With a non-persistent summary and dual publishing enabled, the progress comment is first edited into
the final suggestions result, but self.progress_response still points to it. Cancellation during
the subsequent dual_publishing() await reaches this new handler and removes that final result
instead of a progress comment.
Code

pr_agent/tools/pr_code_suggestions.py[R272-274]

+            if self.progress_response is not None:
+                try:
+                    self.git_provider.remove_comment(self.progress_response)
Relevance

●●● Strong

Cancellation cleanup can remove an edited final result; lifecycle-specific handle clearing is a
concrete correctness fix with no rejection precedent.

PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The run creates and stores a progress handle, then the non-persistent branch edits that same comment
into the final summary without clearing the handle. A later await can propagate cancellation into
the added handler, which unconditionally deletes the object still referenced by that handle.

pr_agent/tools/pr_code_suggestions.py[176-183]
pr_agent/tools/pr_code_suggestions.py[249-261]
pr_agent/tools/pr_code_suggestions.py[271-280]
pr_agent/tools/pr_code_suggestions.py[335-352]

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 cancellation handler can delete an already-published final suggestions summary because `self.progress_response` remains set after the progress comment is converted into the final result.

## Issue Context
In the non-persistent publishing branch, the progress comment is edited into the summary before the later dual-publishing await. Cancellation during that await must not remove the converted comment.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[249-261]
- pr_agent/tools/pr_code_suggestions.py[271-280]

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



Remediation recommended

4. Cleanup failure loses progress handle ✓ Resolved 🐞 Bug ☼ Reliability
Description
The check-run branch returns progress_response regardless of whether _clean_up_progress_note()
actually edited or removed the comment, and run() interprets any non-None return as successful
cleanup and clears self.progress_response. If either cleanup operation fails, a visible progress
comment can remain orphaned with no retained handle for later cleanup or cancellation handling.
Code

pr_agent/tools/pr_code_suggestions.py[R396-397]

+                _clean_up_progress_note()
+                return progress_response
Relevance

●●● Strong

Recent accepted precedents prioritize preserving or neutralizing progress handles when cleanup
fails, matching this orphaned-comment reliability issue.

PR-#2404
PR-#2833

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The moved helper catches cleanup exceptions and returns no status, while the check-run branch
unconditionally returns the non-None progress handle. The caller clears its tracked handle
whenever that return value is non-None; the added test covers only successful edit and removal,
not either failure mode.

pr_agent/tools/pr_code_suggestions.py[379-392]
pr_agent/tools/pr_code_suggestions.py[237-250]
tests/unittest/test_pr_code_suggestions_lifecycle.py[214-219]

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 check-run publishing branch returns the progress comment handle even when `_clean_up_progress_note()` catches an edit or removal failure. The caller then clears `self.progress_response`, potentially orphaning a still-visible progress comment.

## Issue Context
Preserve the progress handle when cleanup fails, or make the cleanup helper return an explicit success status that the caller uses before clearing ownership. Keep the successful check-run path unchanged and preserve best-effort logging.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[379-397]
- pr_agent/tools/pr_code_suggestions.py[237-250]

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


5. edit_comment failure untested 📘 Rule violation ▣ Testability
Description
The new cancellation handler catches and suppresses edit_comment() failures, but the lifecycle
tests only exercise a successful edit and a failing remove_comment(). This leaves the newly
introduced edit-failure branch unverified, contrary to the requirement to test new behavior and
branches.
Code

pr_agent/tools/pr_code_suggestions.py[R279-282]

+                except Exception as cleanup_error:
+                    get_logger().exception(
+                        f"Failed to update code suggestions progress comment after cancellation, "
+                        f"error: {cleanup_error}"
Relevance

●●● Strong

Recent history accepts adding tests for uncovered behavior branches; this explicitly verifies the
new edit-failure cleanup path.

PR-#2653
PR-#2659

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2694678 requires tests for newly introduced production behavior and branches. The
production handler adds an except Exception path for terminal-status editing at
pr_agent/tools/pr_code_suggestions.py[279-283], while the cleanup-failure test configures only
provider.remove_comment.side_effect and asserts a successful edit at
tests/unittest/test_pr_code_suggestions_lifecycle.py[116-141].

Rule 2694678: Require tests to change when production code behavior changes
pr_agent/tools/pr_code_suggestions.py[279-283]
tests/unittest/test_pr_code_suggestions_lifecycle.py[116-141]

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

## Issue description
Add coverage for cancellation cleanup when `edit_comment()` raises.

## Issue Context
The new handler must log the edit failure, still attempt deletion, and re-raise the original `asyncio.CancelledError`. Existing coverage only makes `remove_comment()` fail.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[274-290]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[115-143]

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


6. Failed deletion leaves stale status ✓ Resolved 🐞 Bug ☼ Reliability
Description
The cancellation path attempts deletion without first changing the progress comment to a terminal
state, so a provider/API deletion failure leaves “Preparing suggestions...” or the GFM progress body
visible indefinitely. Logging the failure preserves cancellation but does not prevent the stale
status this cleanup is intended to eliminate.
Code

pr_agent/tools/pr_code_suggestions.py[R272-275]

+            if self.progress_response is not None:
+                try:
+                    self.git_provider.remove_comment(self.progress_response)
+                except Exception as cleanup_error:
Relevance

●●● Strong

Recent accepted cleanup fixes explicitly neutralize progress comments before deletion to prevent
stale status when deletion fails.

PR-#2833
PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The progress comment is published with an explicitly active status, while the added cancellation
handler only removes it; if removal raises, the handler merely logs and the unchanged status
remains. The neighboring description lifecycle already neutralizes its progress comment before a
separately guarded removal, and past accepted fixes identify this same stale-progress failure
pattern.

pr_agent/tools/pr_code_suggestions.py[128-129]
pr_agent/tools/pr_code_suggestions.py[176-183]
pr_agent/tools/pr_code_suggestions.py[271-280]
pr_agent/tools/pr_description.py[222-235]
PR-#2833
PR-#2404

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 failed deletion leaves the cancellation progress comment displaying an active in-progress state indefinitely.

## Issue Context
Best-effort cleanup should first edit the tracked progress comment to a benign cancellation/finished message, then independently attempt deletion, while neither cleanup failure may replace the original `CancelledError`.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[271-280]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[75-100]

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



Informational

7. Handle comment is non-imperative 📘 Rule violation ⚙ Maintainability ⭐ New
Description
The new comment narrates state with “The handle is retained” instead of using an imperative
instruction. This violates the required phrasing convention for behavior-describing comments.
Code

tests/unittest/test_pr_code_suggestions_lifecycle.py[258]

+        # The handle is retained so a later cancellation or error handler can retry removal
Relevance

● Weak

Recent same-rule precedent rejected imperative rewrites for narrative behavioral comments in tests.

PR-#2785

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2694688 requires behavior-describing comments to use imperative phrasing. The added comment at
line 258 uses passive descriptive phrasing: The handle is retained....

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

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

## Issue description
Rewrite the behavior-describing comment in imperative mood rather than passive narrative phrasing.

## Issue Context
PR Compliance ID 2694688 requires newly added comments that describe behavior to use command/instruction phrasing. For example: `# Retain the handle so a later cancellation or error handler can retry removal.`

## Fix Focus Areas
- tests/unittest/test_pr_code_suggestions_lifecycle.py[258-258]

ⓘ 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

Context sources
✅ Compliance rules (platform): 34 rules
Review mode: ⚖️ Balanced: This is a localized but behavior-changing provider cleanup path involving cancellation/error handling and comment-handle semantics, so it warrants a careful single-pass review; it is not dense enough for extended.

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

Previous reviews

Review updated until commit 5bb7f7d

Results up to commit cd1ef4b ⚖️ Balanced


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


Action required
1. Cancellation deletes final summary ✓ Resolved 🐞 Bug ≡ Correctness
Description
With a non-persistent summary and dual publishing enabled, the progress comment is first edited into
the final suggestions result, but self.progress_response still points to it. Cancellation during
the subsequent dual_publishing() await reaches this new handler and removes that final result
instead of a progress comment.
Code

pr_agent/tools/pr_code_suggestions.py[R272-274]

+            if self.progress_response is not None:
+                try:
+                    self.git_provider.remove_comment(self.progress_response)
Relevance

●●● Strong

Cancellation cleanup can remove an edited final result; lifecycle-specific handle clearing is a
concrete correctness fix with no rejection precedent.

PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The run creates and stores a progress handle, then the non-persistent branch edits that same comment
into the final summary without clearing the handle. A later await can propagate cancellation into
the added handler, which unconditionally deletes the object still referenced by that handle.

pr_agent/tools/pr_code_suggestions.py[176-183]
pr_agent/tools/pr_code_suggestions.py[249-261]
pr_agent/tools/pr_code_suggestions.py[271-280]
pr_agent/tools/pr_code_suggestions.py[335-352]

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 cancellation handler can delete an already-published final suggestions summary because `self.progress_response` remains set after the progress comment is converted into the final result.

## Issue Context
In the non-persistent publishing branch, the progress comment is edited into the summary before the later dual-publishing await. Cancellation during that await must not remove the converted comment.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[249-261]
- pr_agent/tools/pr_code_suggestions.py[271-280]

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



Remediation recommended
2. Failed deletion leaves stale status ✓ Resolved 🐞 Bug ☼ Reliability
Description
The cancellation path attempts deletion without first changing the progress comment to a terminal
state, so a provider/API deletion failure leaves “Preparing suggestions...” or the GFM progress body
visible indefinitely. Logging the failure preserves cancellation but does not prevent the stale
status this cleanup is intended to eliminate.
Code

pr_agent/tools/pr_code_suggestions.py[R272-275]

+            if self.progress_response is not None:
+                try:
+                    self.git_provider.remove_comment(self.progress_response)
+                except Exception as cleanup_error:
Relevance

●●● Strong

Recent accepted cleanup fixes explicitly neutralize progress comments before deletion to prevent
stale status when deletion fails.

PR-#2833
PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The progress comment is published with an explicitly active status, while the added cancellation
handler only removes it; if removal raises, the handler merely logs and the unchanged status
remains. The neighboring description lifecycle already neutralizes its progress comment before a
separately guarded removal, and past accepted fixes identify this same stale-progress failure
pattern.

pr_agent/tools/pr_code_suggestions.py[128-129]
pr_agent/tools/pr_code_suggestions.py[176-183]
pr_agent/tools/pr_code_suggestions.py[271-280]
pr_agent/tools/pr_description.py[222-235]
PR-#2833
PR-#2404

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 failed deletion leaves the cancellation progress comment displaying an active in-progress state indefinitely.

## Issue Context
Best-effort cleanup should first edit the tracked progress comment to a benign cancellation/finished message, then independently attempt deletion, while neither cleanup failure may replace the original `CancelledError`.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[271-280]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[75-100]

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


Results up to commit e88c5f5 ⚖️ Balanced


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


Action required
1. Persistent summary remains cleanup-owned ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new ownership reset only runs in the non-persistent branch, while the persistent fallback can
edit the same progress_response into the final summary without clearing self.progress_response.
If dual_publishing() is then cancelled, the cancellation handler treats that final summary as
progress, overwrites it with the cancellation message, and removes it.
Code

pr_agent/tools/pr_code_suggestions.py[256]

+                            self.progress_response = None
Relevance

●●● Strong

Recent PR #2404 accepted protecting persistent publication from progress cleanup, directly matching
this ownership regression.

PR-#2404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
run() passes the tracked progress handle into persistent publishing, but the newly added reset
exists only in the else branch for non-persistent publication. The persistent helper's fallback
explicitly converts that progress comment into the new final persistent comment and returns it;
afterward run() awaits dual publishing, and its cancellation handler cleans any still-tracked
handle. The new regression test forces persistent_comment = False, so it does not exercise this
default-enabled persistent path.

pr_agent/tools/pr_code_suggestions.py[236-262]
pr_agent/tools/pr_code_suggestions.py[547-557]
pr_agent/tools/pr_code_suggestions.py[272-291]
tests/unittest/test_pr_code_suggestions_lifecycle.py[98-110]
pr_agent/settings/configuration.toml[169-179]
PR-#2404

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 progress handle is relinquished only after non-persistent summary publication. When persistent publication creates its first summary by editing that same progress comment, cancellation during subsequent dual publishing still overwrites and deletes the completed summary.

## Issue Context
Track whether `publish_persistent_comment_with_history()` promoted `progress_response` into the persistent summary, and clear or otherwise transfer that handle before awaiting dual publishing. Preserve cleanup behavior for a progress note that remains separate from an already-existing persistent thread, and add coverage for persistent mode with no prior summary comment.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[236-262]
- pr_agent/tools/pr_code_suggestions.py[547-557]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[78-114]

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



Remediation recommended
2. edit_comment failure untested 📘 Rule violation ▣ Testability
Description
The new cancellation handler catches and suppresses edit_comment() failures, but the lifecycle
tests only exercise a successful edit and a failing remove_comment(). This leaves the newly
introduced edit-failure branch unverified, contrary to the requirement to test new behavior and
branches.
Code

pr_agent/tools/pr_code_suggestions.py[R279-282]

+                except Exception as cleanup_error:
+                    get_logger().exception(
+                        f"Failed to update code suggestions progress comment after cancellation, "
+                        f"error: {cleanup_error}"
Relevance

●●● Strong

Recent history accepts adding tests for uncovered behavior branches; this explicitly verifies the
new edit-failure cleanup path.

PR-#2653
PR-#2659

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2694678 requires tests for newly introduced production behavior and branches. The
production handler adds an except Exception path for terminal-status editing at
pr_agent/tools/pr_code_suggestions.py[279-283], while the cleanup-failure test configures only
provider.remove_comment.side_effect and asserts a successful edit at
tests/unittest/test_pr_code_suggestions_lifecycle.py[116-141].

Rule 2694678: Require tests to change when production code behavior changes
pr_agent/tools/pr_code_suggestions.py[279-283]
tests/unittest/test_pr_code_suggestions_lifecycle.py[116-141]

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

## Issue description
Add coverage for cancellation cleanup when `edit_comment()` raises.

## Issue Context
The new handler must log the edit failure, still attempt deletion, and re-raise the original `asyncio.CancelledError`. Existing coverage only makes `remove_comment()` fail.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[274-290]
- tests/unittest/test_pr_code_suggestions_lifecycle.py[115-143]

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


Results up to commit d9b3064 🚀 Fast


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


Action required
1. Check-run path leaves progress comment ✓ Resolved 🐞 Bug ≡ Correctness
Description
When publish_persistent_comment_with_history() successfully publishes a check run, it returns
None without cleaning the supplied progress comment; the new is not None guard therefore leaves
self.progress_response set. A normal run then exits without deleting that comment, so the
temporary progress status remains visible indefinitely (and cancellation can later treat it as
still-owned progress).
Code

pr_agent/tools/pr_code_suggestions.py[R249-250]

+                        if published_comment is not None:
+                            self.progress_response = None
Relevance

●●● Strong

Recent accepted precedents require clearing or cleaning progress handles after persistent
publication; this is the same stale-comment bug.

PR-#2404
PR-#2833

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed guard clears ownership only for a non-None return. In the same branch's callee, a
successful _publish_check_run() causes an immediate bare return, while the caller's normal
persistent path has no subsequent progress cleanup; only the cancellation handler later acts on the
retained handle.

pr_agent/tools/pr_code_suggestions.py[249-250]
pr_agent/tools/pr_code_suggestions.py[379-381]
pr_agent/tools/pr_code_suggestions.py[262-269]
pr_agent/tools/pr_code_suggestions.py[274-287]

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

## Issue description
Successful check-run publication returns `None`, so the new conditional does not clear or otherwise clean the provider-owned progress comment. This leaves the temporary progress comment visible after a normal persistent run.

## Issue Context
`publish_persistent_comment_with_history()` returns early when `_publish_check_run()` succeeds, before its comment-update/cleanup logic. The caller must handle that success path explicitly without confusing it with a failed publication.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[249-250]
- pr_agent/tools/pr_code_suggestions.py[379-381]
- pr_agent/tools/pr_code_suggestions.py[420-433]

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


Results up to commit 15f6b9f 🚀 Fast


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


Remediation recommended
1. Cleanup failure loses progress handle ✓ Resolved 🐞 Bug ☼ Reliability
Description
The check-run branch returns progress_response regardless of whether _clean_up_progress_note()
actually edited or removed the comment, and run() interprets any non-None return as successful
cleanup and clears self.progress_response. If either cleanup operation fails, a visible progress
comment can remain orphaned with no retained handle for later cleanup or cancellation handling.
Code

pr_agent/tools/pr_code_suggestions.py[R396-397]

+                _clean_up_progress_note()
+                return progress_response
Relevance

●●● Strong

Recent accepted precedents prioritize preserving or neutralizing progress handles when cleanup
fails, matching this orphaned-comment reliability issue.

PR-#2404
PR-#2833

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The moved helper catches cleanup exceptions and returns no status, while the check-run branch
unconditionally returns the non-None progress handle. The caller clears its tracked handle
whenever that return value is non-None; the added test covers only successful edit and removal,
not either failure mode.

pr_agent/tools/pr_code_suggestions.py[379-392]
pr_agent/tools/pr_code_suggestions.py[237-250]
tests/unittest/test_pr_code_suggestions_lifecycle.py[214-219]

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 check-run publishing branch returns the progress comment handle even when `_clean_up_progress_note()` catches an edit or removal failure. The caller then clears `self.progress_response`, potentially orphaning a still-visible progress comment.

## Issue Context
Preserve the progress handle when cleanup fails, or make the cleanup helper return an explicit success status that the caller uses before clearing ownership. Keep the successful check-run path unchanged and preserve best-effort logging.

## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[379-397]
- pr_agent/tools/pr_code_suggestions.py[237-250]

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


Grey Divider

Qodo Logo

Comment thread pr_agent/tools/pr_code_suggestions.py
Signed-off-by: hwan <3373484735@qq.com>
Comment thread pr_agent/tools/pr_code_suggestions.py
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

Signed-off-by: hwan <3373484735@qq.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

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

Merging once the suggestion is in. Qodo's "persistent summary remains cleanup-owned" is resolved for the first run, but the identity check leaves the second run open, and all four of your tests return [] from get_issue_comments, so none reach it.

On a second run the publish edits the existing persistent comment, _clean_up_progress_note deletes the progress note, and the call returns the persistent comment, so is does not match. Cancellation then edits and removes a comment that is already gone: two 404s, each logged with a full traceback. With the suggestion applied the edit disappears and remove_comment goes from two calls to one.

A test for that path would be good, and it would sit next to the edit-failure branch Qodo still has open.

Comment thread pr_agent/tools/pr_code_suggestions.py Outdated
identity_marker=PRCodeSuggestionsIdentity.SUMMARY.value,
legacy_initial_header=PRCodeSuggestionsHeader.SUMMARY.value,
)
if published_comment is self.progress_response:

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.

The publish returns the persistent comment on a second run, not the progress note, so this never fires and the handler later touches a comment _clean_up_progress_note already deleted. Checking for any returned comment covers both runs, and still leaves the note alone on the check-run early return, which returns None.

Suggested change
if published_comment is self.progress_response:
if published_comment is not None:

Address review on The-PR-Agent#2850: publish_persistent_comment_with_history
returns the pre-existing persistent comment on a second run, not the
progress note, so the identity check never fired and the cancellation
handler could touch a comment _clean_up_progress_note already removed.
Checking for any returned comment covers both runs and still leaves the
progress handle intact for the check-run early return, which yields
None.
Comment thread pr_agent/tools/pr_code_suggestions.py
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

Follow-up to d9b3064: the check-run early return in
publish_persistent_comment_with_history yields None, so the
'is not None' guard left self.progress_response pointing at a
comment _clean_up_progress_note never ran on that path. The progress
status then stayed visible indefinitely after a normal run. Move
_clean_up_progress_note above the check-run branch, invoke it before
returning, and return progress_response so the caller drops the handle
rather than leaving a dangling reference. Covers the path with
test_run_cleans_up_progress_comment_on_check_run_publish.
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 15f6b9f

Address review on The-PR-Agent#2850: the check-run branch returned
progress_response unconditionally, so run() cleared
self.progress_response even when _clean_up_progress_note() caught an
edit or removal failure and the comment stayed visible — orphaning it
with no handle for later cleanup or cancellation. Return None on
cleanup failure so the caller keeps the tracked handle; the successful
check-run path is unchanged. Covered by
test_run_retains_progress_handle_when_check_run_cleanup_fails.
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 5bb7f7d

@junnhwan

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion @IsmaelMartinez — applied in d9b3064.

While testing I noticed the check-run early return in publish_persistent_comment_with_history() also yields None, so the is not None guard left self.progress_response pointing at a progress comment that _clean_up_progress_note() never ran on that path. I moved the helper above the check-run branch and invoked it before returning (15f6b9f).

A follow-up review flagged that returning progress_response unconditionally could orphan a visible progress comment when the edit/remove inside the helper fails, since run() would still clear its tracked handle. The helper now returns a bool and the check-run branch only relinquishes the handle when cleanup succeeded (5bb7f7d). Failure keeps the handle so the cancellation/error handler can still reach it.

Added coverage for both the successful check-run cleanup and the failed-cleanup case. Lifecycle suite is 7 passed; core/identity suites are 83 passed. CI is green.

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

Merging, as promised once the suggestion landed. Clearing progress_response means the cancellation handler's guard short-circuits, so a second run no longer edits and removes a comment that is already gone. All seven go red on revert against today's main.

Two things I am not holding this for, both going into one follow-up issue: the second-run test I asked about is still missing, and the except Exception handler still reads a cleared progress_response as "nothing was published", so it can post a failure comment over suggestions that published fine.

Thanks for the quick turnarounds here and on #2865.

@IsmaelMartinez
IsmaelMartinez merged commit ab0cddb into The-PR-Agent:main Aug 29, 2026
5 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.

/improve leaves its progress comment after task cancellation

2 participants