Skip to content

fix: resolve issue # - - #291

Closed
matthiasL-scality wants to merge 1 commit into
mainfrom
fix/issue-290
Closed

matthiasL-scality wants to merge 1 commit into
mainfrom
fix/issue-290

Conversation

@matthiasL-scality

Copy link
Copy Markdown
Contributor

Automated Fix for Issue

Issue:

Description:


Changes Made

This PR was automatically generated by Claude Code based on the issue analysis.

AI Evaluation

  • Relevance:
  • Confidence: %
  • Reason:

Files Modified

[]

Tests Created

[]


Generated by n8n automation workflow

@matthiasL-scality
matthiasL-scality requested a review from a team as a code owner October 1, 2026 08:14
@upsun-dispatch

upsun-dispatch Bot commented Oct 1, 2026

Copy link
Copy Markdown

📋 PR Summary

With this change, Bert-E edits its previous status comment on a pull request instead of posting a new comment each time. A new updatable flag marks which messages count as status updates: InformationException and its subclasses, IncorrectFixVersion, BranchHistoryMismatch and ResetComplete. When one of these is sent, the bot finds its latest comment that ends with a hidden <!-- bert-e:status --> marker and edits that comment. If the text hasn't changed it doesn't post anything. If the git host can't edit comments, it posts a new comment instead.

Changes
Layer / File(s) Summary
Status message flagging
bert_e/exceptions.py Adds an updatable class attribute to TemplateException, off by default. It is turned on for InformationException, IncorrectFixVersion, BranchHistoryMismatch and ResetComplete.
Comment editing support
bert_e/git_host/base.py Adds a default AbstractComment.edit(msg) that raises NotImplementedError, for git hosts that can't edit comments.
bert_e/git_host/github/__init__.py Adds GitHub comment editing: edit sends an update request with the new body and stores the returned data.
bert_e/git_host/mock.py Adds edit to the mock CommentController, which replaces the comment's content.
Comment posting logic
bert_e/workflow/pr_utils.py Adds STATUS_MARKER and find_status_comment. _send_comment gets an updatable parameter that adds the marker to the message and edits the existing status comment in place. It raises CommentAlreadyExists when the text is the same, and posts a new comment if editing isn't supported. notify_user now passes each message's updatable flag.
Tests
bert_e/tests/unit/test_comment_update.py New unit tests covering: which classes are flagged, in-place updates, identical statuses not being re-posted, non-updatable messages still being posted, the fallback when editing isn't supported, and comments by other authors being left alone.

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

1 critical finding in this review. Details below.

Caution

Changes requested — 🔴 1 critical · 🟡 3 warnings

🔍 Full review · 6 files reviewed

Verification
  • AbstractComment.edit defaults to raising NotImplementedError, and _send_comment catches it and falls back to add_comment, so Bitbucket-style hosts keep the old behaviour.
  • The GitHub Comment.edit PATCHes self.data['url'] through update, and that url field is part of schema.Comment, so it is loaded.
  • find_status_comment only matches comments by settings.robot, so the marker in another user's comment is ignored.
  • The CommentAlreadyExists raised for an unchanged status is caught by notify_user, the same way the existing dedup path is.

The new bert_e/tests/unit/test_comment_update.py drives _send_comment with FakePR/FakeComment stubs only. No workflow-level test covers how editing comments interacts with send_greetings or with command handling in handle_comments, and that interaction is where the reset loop happens.

Review details
  • Commit: 23cb096
  • Model: claude-opus-5-5

Review 1 of 10 for this pull request · View the full run

)
try:
LOG.debug('UPDATING STATUS COMMENT %s', msg)
status_comment.edit(msg)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical — PRs loop on reset forever and can never merge.

handle_comments only treats commands posted after the bot's last comment as new: it walks reversed(pull_request.comments) and returns at the first comment by the robot (gitwaterflow/init.py ~L344). When a status is updatable, _send_comment now edits an older status comment instead of appending a new one. The bot then has no comment after the triggering command, so the command runs again on every later webhook.

Concrete case:

  1. A user posts @bert-e reset.
  2. _reset raises ResetComplete, which is updatable.
  3. The bot edits the InitMessage, which is the first comment and carries the marker. No new bot comment is added.
  4. On the next run handle_commands finds @bert-e reset again and deletes the integration branches again. It raises ResetComplete again, which is now swallowed as CommentAlreadyExists.

The flow stops at the reset on every run, so the PR never reaches integration or queuing. The same thing happens for any command whose answer is an updatable message.

Comment thread bert_e/exceptions.py
# template for informative exceptions
class InformationException(TemplateException):
dont_repeat_if_in_history = NEVER_REPEAT
updatable = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning — Users lose the bot's manual and greeting on every PR.

InformationException.updatable = True makes InitMessage updatable. InitMessage goes through notify_user, so the marker is appended to the greeting, and the greeting becomes the status comment. The first later updatable message (IntegrationDataCreated, ResetComplete, BranchHistoryMismatch, IncorrectFixVersion, PendingHotfixVersionReminder) edits the greeting in place. That wipes the welcome text and the list of available options and commands from the PR.

return

if updatable:
status_comment = find_status_comment(pull_request, settings.robot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning — Blocking failures become invisible to PR authors.

find_status_comment returns the newest marked comment, however far back in the thread it is. Non-updatable messages such as ApprovalRequired, Conflict, BuildFailed and Queued are posted as new comments after it. So a new updatable failure, for example BranchHistoryMismatch or IncorrectFixVersion, silently rewrites a comment that may sit many comments above the current discussion, often the original greeting.

Git hosts do not notify anyone on comment edits. The PR author gets no visible message that their PR is now blocked, and the latest visible bot comment can be an outdated status.

Comment thread bert_e/exceptions.py
# template for informative exceptions
class InformationException(TemplateException):
dont_repeat_if_in_history = NEVER_REPEAT
updatable = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning — The status comment flip-flops and loses important messages.

PendingHotfixVersionReminder inherits updatable = True. check_pending_hotfix in jira.py skips the reminder only if a bot comment still starts with _PENDING_HOTFIX_TITLE.

  1. The reminder edits the shared status comment.
  2. Any later updatable status, such as ResetComplete or IntegrationDataCreated, overwrites that comment and removes the title.
  3. The dedup no longer finds the title, so the next run edits the reminder back in, replacing the other status.

The reminder and the other statuses keep overwriting each other in the same comment.

Comment thread bert_e/exceptions.py
# template for informative exceptions
class InformationException(TemplateException):
dont_repeat_if_in_history = NEVER_REPEAT
updatable = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

InitMessage inherits updatable = True from InformationException, which means the initial welcome/instructions comment will be silently replaced in place by the next updatable status message (e.g. IntegrationDataCreated). This is likely unintended — the init message serves a different purpose than status updates. Consider either overriding updatable = False on InitMessage, or setting updatable = True only on specific subclasses rather than on the base class.

— Claude Code

LOG.debug('UPDATING STATUS COMMENT %s', msg)
status_comment.edit(msg)
return
except NotImplementedError:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The except NotImplementedError only catches the case where the git host does not support editing. If the status comment was deleted by a user between find_status_comment() and edit(), an HTTP error (e.g. 404) would propagate uncaught and crash the notification flow instead of falling back to posting a new comment. Consider also catching requests.HTTPError (or a broader exception with a log warning) so the fallback works for API failures too.

— Claude Code

@claude

claude Bot commented Oct 1, 2026

Copy link
Copy Markdown
  • InitMessage unintentionally inherits updatable = True from InformationException, so the welcome/instructions comment will be replaced in place by the next status update. Either override on InitMessage or move updatable = True to specific subclasses.
    - Set updatable = False on InitMessage, or remove updatable = True from InformationException and set it explicitly on IntegrationDataCreated and PendingHotfixVersionReminder.
    - The except NotImplementedError in _send_comment does not catch HTTP errors from the API. If a user deletes the status comment between find_status_comment() and edit(), the resulting HTTP 404 would crash the notification flow instead of falling back to posting a new comment.
    - Broaden the except clause to also catch requests.HTTPError or Exception with a warning log.

    Review by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants