Skip to content

fix: resolve issue #297 - #298

Closed
matthiasL-scality wants to merge 3 commits into
mainfrom
fix/issue-297
Closed

matthiasL-scality wants to merge 3 commits into
mainfrom
fix/issue-297

Conversation

@matthiasL-scality

Copy link
Copy Markdown
Contributor

Maintain a single bot-owned status comment, edited in place (latest state + integration PRs), created before the first Bert-E message. Adds AbstractComment.update (GitHub + mock).

@matthiasL-scality
matthiasL-scality requested a review from a team as a code owner October 2, 2026 06:01
Comment thread bert_e/git_host/base.py Outdated
"""Abstract class defining the interface of a pull requests's comment."""

@abstractmethod
def update(self, text: str) -> None:

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 name update clashes with the AbstractGitHostObject.update(cls, client, data, ...) classmethod (base.py:199). Bitbucket Comment does not override it, so ABC sees the classmethod and does not flag it as abstract. At runtime existing.update(text) calls the classmethod with client=text and raises TypeError, which notify_user swallows. Result: on Bitbucket the status comment is never updated and every run logs a warning.
Implement update on bitbucket.Comment (PUT {'content': {'raw': text}} to the comment URL), or give the method a name that doesn't clash, like edit.

— Claude Code

Comment thread bert_e/workflow/pr_utils.py Outdated
# the last bot comment (see dont_repeat_if_in_history).
try:
update_status_comment(settings, pull_request, comment)
except Exception:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Broad except Exception hides real bugs here, such as the Bitbucket update TypeError. Catch the expected failures instead (requests.HTTPError, git_host errors) so that programming errors still surface in tests.

— Claude Code

status=getattr(comment, 'status', None),
integration_prs=prs,
active_options=comment.kwargs.get('active_options'))
if existing is None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On PRs that already have bot comments, the first run after deploy posts the status comment at the bottom instead of near the top. It then becomes the last bot comment, so for messages with dont_repeat_if_in_history=-1 (the default), find_comment(..., max_history=-1) sees a non-matching comment first, returns None, and the previous message gets posted again. Create the status comment only when the PR has no bot comment yet, or make find_comment skip comments that start with STATUS_COMMENT_MARKER.

— Claude Code

try:
# Must happen before the message so that the status comment is never
# the last bot comment (see dont_repeat_if_in_history).
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Every bot notification now adds an extra comment at position 0, but the existing integration tests in test_bert_e.py were not updated. They count comments (assertIs(len(list(pr.get_comments())), 1) at L1354, plus L1390/1400/1551/1590-1613/3536-3604), and test_init_message reads pr.comments[0] expecting the greeting. These should fail in the TestBertE jobs. Update those assertions, and add an integration test that runs through the mock host instead of only FakePR.

— Claude Code

@claude

claude Bot commented Oct 2, 2026

Copy link
Copy Markdown
  • On Bitbucket, AbstractComment.update resolves to the AbstractGitHostObject.update classmethod, so status updates fail with TypeError, which is then swallowed
    - Implement update on bitbucket.Comment, or rename the method (e.g. edit)
    - The broad except Exception in notify_user hides programming errors
    - Catch only HTTP/git-host errors
    - On existing PRs the status comment is created as the last bot comment, so the dont_repeat_if_in_history=-1 check stops working and the previous message is posted again
    - Create it only when the PR has no bot comment yet, or make find_comment skip the marker comment
    - Existing test_bert_e.py assertions on comment counts and pr.comments[0] (e.g. test_init_message) were not updated for the extra comment
    - Update them, and add a mock-host integration test

    Review by Claude Code

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

Warning

Changes suggested — 🟡 5 warnings

🔍 Full review · 7 files reviewed

Verification
  • The GitHub Comment.update instance method overrides the inherited classmethod, and client.patch returns parsed JSON whose body/user/id/url keys match the properties.
  • STATUS_PR_RE uses \u2192 in a raw str pattern, which re interprets correctly, and the -%} in the template keeps each PR line starting with * #.
  • The status comment never starts with the bot's message text, so find_comment dedup with max_history=None (NEVER_REPEAT messages) is unaffected.
  • handle_comments command scanning stops at the newest bot comment; an in-place edit does not change GitHub ordering, so new commands after the last message are still seen.

The PR adds tests: bert_e/tests/test_pr_status_comment.py, which uses a fake PR/comment and never touches a real git host or the Bitbucket client, and bert_e/tests/unit/test_status_template.py. Nothing covers the Bitbucket path or the reposting on PRs that already have bot comments, and the existing comment-count and first-comment assertions in test_bert_e.py were not updated.

Review details
  • Commit: 413b17d
  • Model: claude-opus-5-5

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

Comment thread bert_e/git_host/base.py Outdated
Comment thread bert_e/workflow/pr_utils.py
Comment thread bert_e/workflow/pr_utils.py
Comment thread bert_e/workflow/pr_utils.py
if child_prs:
return [{'id': pr.id, 'dst': pr.dst_branch, 'src': pr.src_branch}
for pr in child_prs]
if previous_text:

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 — Stale integration PR links stay pinned and point users at closed PRs.

When the current message has no child_prs, _integration_pull_requests copies the list from the previous status comment's text. Only IntegrationDataCreated ever passes child_prs, and it is sent only when a PR is newly created and there are at least 2 wbranches. So the list never shrinks or refreshes. After /reset, a declined or recreated integration PR, or a PR whose cascade drops to one branch, the comment keeps listing the old PR numbers indefinitely.

Comment thread bert_e/workflow/pr_utils.py Outdated
"""
if settings.no_comment or settings.interactive:
return
if getattr(comment, 'status', None) is None:

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 integration PR list will never show up in production. child_prs is only passed by IntegrationDataCreated (notify_integration_data), which is an InformationException with status = None, so this early return skips it. Every later message with a status has no child_prs, and the previous comment has no PRs to carry over either.
Let messages that carry child_prs through this check (for example if comment.status is None and not comment.kwargs.get('child_prs')), and keep the previous state/status when the message has no status of its own.

— Claude Code

Comment thread bert_e/tests/test_pr_status_comment.py Outdated
child = SimpleNamespace(id=7, src_branch='w/1.1/x',
dst_branch='development/1.1')
err = _conflict()
err.kwargs['child_prs'] = [child]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This test puts child_prs on an IncompatibleSourceBranchPrefix error, and that never happens in the real flow. It passes even though the real flow never fills in the integration PR list. Test it with exceptions.IntegrationDataCreated instead, or add an integration test in test_bert_e.py that checks the status comment lists the w/* PRs after create_integration_pull_requests.

— Claude Code

status=getattr(comment, 'status', None),
integration_prs=prs,
active_options=comment.kwargs.get('active_options'))
if existing is None:

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 comment is created the first time a message has a status, not before the first Bert-E message. On new PRs it lands after the greeting (InitMessage has no status). On PRs that already exist when this is deployed, it lands at the bottom of the thread and stays there, so it is not "pinned" early. Either create it from send_greetings before the InitMessage, or change the PR description and docstring to match what the code does.

— Claude Code

@claude

claude Bot commented Oct 2, 2026

Copy link
Copy Markdown
  • The integration PR list in the status comment is never filled in for real. child_prs only comes with IntegrationDataCreated, which has status = None, and update_status_comment returns early for those messages (pr_utils.py:151).
    - Let messages that carry child_prs through the early return, and keep the previous state when the message has no status.
    - Fix test_integration_prs_kept_between_updates: it attaches child_prs to a failure message, which hides the bug. Test with IntegrationDataCreated or add an end-to-end test in test_bert_e.py.
    - The status comment is not placed early as the description says. It lands after the greeting on new PRs, and at the bottom of the thread on existing PRs (pr_utils.py:163).
    - Create it in send_greetings before InitMessage, or update the description and docstring.
    - The PR description mentions AbstractComment.update, but the method is named edit.
    - Update the description.

    Review by Claude Code

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

Warning

Changes suggested — 🟡 2 warnings · 2 still open

🔁 Incremental · 7 files reviewed

Outstanding from earlier reviews:

  • 🟡 #4163260446 — bert_e/workflow/pr_utils.py:174: The existing test suite fails and blocks the change. — Several count assertions were changed by +1. I could not run them to confirm each now matches, given which messages create the status comment (status-bearing only).
  • 🟡 #4163260460 — bert_e/workflow/pr_utils.py:132: Stale integration PR links stay pinned and point users at closed PRs.
Verification
  • find_comment now skips comments starting with STATUS_COMMENT_MARKER, so a status comment added after older bot comments no longer breaks the max_history=-1 dedup check.
  • Bitbucket Comment.edit passes full_name, pull_request_id and comment_id, which fill every placeholder in GET_URL, the URL update falls back to.
  • The edit rename is consistent across AbstractComment, the GitHub, Bitbucket and mock implementations, and the test FakeComment; update is no longer called on comments anywhere.
  • InitMessage has no status, so the greeting still comes first and test_init_message's pr.comments[0] check is unaffected.

New unit tests in bert_e/tests/test_pr_status_comment.py cover create/edit, skipping informational messages and the dedup skip. They use FakePR only, and they inject child_prs on a message that never carries them in real code. Changed count assertions in test_bert_e.py run under the tox jobs in .github/workflows/main.yaml. Nothing tests Bitbucket Comment.edit against the real API shape.

Review details

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

Comment thread bert_e/workflow/pr_utils.py Outdated
"""
if settings.no_comment or settings.interactive:
return
if getattr(comment, 'status', None) is None:

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 never shows integration PRs, which the feature promises.

The new early return skips every message whose status is None. IntegrationDataCreated is an InformationException with no status, and it is the only message that carries child_prs (notify_integration_data in gitwaterflow/integration.py:217-225). So update_status_comment returns before _integration_pull_requests ever sees child_prs. Every status-bearing message has no child_prs and no earlier list to copy, so in production the pinned comment never lists integration PRs, which is half of what the PR sets out to do. test_integration_prs_kept_between_updates hides this by putting child_prs on an IncompatibleSourceBranchPrefix, which never happens in real code.

Comment thread bert_e/git_host/bitbucket/__init__.py Outdated
@codecov

codecov Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.17241% with 65 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.76%. Comparing base (cfe0bc2) to head (e3ab863).

Files with missing lines Patch % Lines
bert_e/tests/test_pr_status_comment.py 0.00% 58 Missing ⚠️
bert_e/git_host/bitbucket/__init__.py 25.00% 3 Missing ⚠️
bert_e/workflow/pr_utils.py 93.87% 3 Missing ⚠️
bert_e/git_host/github/__init__.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #298      +/-   ##
==========================================
- Coverage   90.21%   89.76%   -0.46%     
==========================================
  Files          82       84       +2     
  Lines       11293    11430     +137     
==========================================
+ Hits        10188    10260      +72     
- Misses       1105     1170      +65     
Flag Coverage Δ
integration 87.68% <89.70%> (+<0.01%) ⬆️
tests 87.65% <89.70%> (+<0.01%) ⬆️
tests-BuildFailedTest 25.72% <23.52%> (+<0.01%) ⬆️
tests-QuickTest 32.97% <23.52%> (-0.05%) ⬇️
tests-RepositoryTests 25.40% <23.52%> (+<0.01%) ⬆️
tests-TaskQueueTests 49.88% <77.94%> (+0.27%) ⬆️
tests-TestBertE 67.12% <89.70%> (+0.14%) ⬆️
tests-TestQueueing 51.88% <69.11%> (+0.19%) ⬆️
tests-api-mock 14.31% <3.44%> (-0.13%) ⬇️
tests-noqueue 78.45% <89.70%> (+0.06%) ⬆️
tests-noqueue-BuildFailedTest 25.72% <23.52%> (+<0.01%) ⬆️
tests-noqueue-QuickTest 32.97% <23.52%> (-0.05%) ⬇️
tests-noqueue-RepositoryTests 25.40% <23.52%> (+<0.01%) ⬆️
tests-noqueue-TaskQueueTests 49.88% <77.94%> (+0.27%) ⬆️
tests-noqueue-TestBertE 63.71% <89.70%> (+0.16%) ⬆️
tests-noqueue-TestQueueing 25.42% <23.52%> (+<0.01%) ⬆️
tests-server 26.53% <11.03%> (-0.19%) ⬇️
unittests 43.20% <24.13%> (-0.22%) ⬇️
utests 29.05% <24.13%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

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

Warning

Changes suggested — 🟡 1 warning · 2 still open

🔁 Incremental · 3 files reviewed

Outstanding from earlier reviews:

  • 🟡 #4163260460 — bert_e/workflow/pr_utils.py:136: Stale integration PR links stay pinned and point users at closed PRs. — _integration_pull_requests still copies the earlier list whenever a message has no child_prs, so declined or recreated integration PRs stay listed.
  • 🟡 #4164452676 — bert_e/workflow/pr_utils.py:151: The status comment never shows integration PRs, which the feature promises. — Partly fixed: integration PRs show up only if a status comment already exists, which a clean PR does not have when IntegrationDataCreated is sent.
Verification
  • Bitbucket Comment.edit now PUTs {'content': {'raw': text}} to GET_URL and load(response) calls raise_for_status, so failures surface as HTTPError, which notify_user catches.
  • STATUS_STATE_RE and STATUS_RESULT_RE match the ## Bert-E status: and Result: **...** lines the template renders, so state and status carry over on an integration-only update.
  • Passing code=None does nothing harmful: pr_status_comment.md never references code.
  • find_comment now skips comments that start with STATUS_COMMENT_MARKER, so a status comment appended at the bottom no longer breaks the max_history=-1 duplicate check.

The changed test_integration_prs_kept_between_updates in bert_e/tests/test_pr_status_comment.py now uses a real IntegrationDataCreated, but it seeds a status comment first. No test covers integration data that arrives before any status comment, and none covers Bitbucket Comment.edit.

Review details

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

if status is None:
# only the integration pull requests changed: keep the state
if existing is None:
return

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 — Integration PR list missing on most PRs, defeating half the feature.

The new if existing is None: return throws away the child_prs of an IntegrationDataCreated message whenever no status comment exists yet. That is the normal case on a clean PR. InitMessage carries no status, so the first run creates no status comment. When every check passes, the next message is notify_integration_data (gitwaterflow/init.py:198-200), which hits this return. Later status messages such as ApprovalRequired have no child_prs, and there is no earlier comment text for _integration_pull_requests to copy from, so the status comment gets created with no integration PR list. IntegrationDataCreated is sent only when a w/ branch or PR is newly created, so later runs never recover the list. The list therefore appears only on PRs that hit a status-bearing error before their integration PRs were created. test_integration_prs_kept_between_updates hides this because it calls update_status_comment(..., _conflict()) first to seed a status comment. Fix: create the comment here with a neutral state instead of returning.

if status is None:
# only the integration pull requests changed: keep the state
if existing is None:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On a fresh PR with automatic integration branch creation, IntegrationDataCreated is the first message that matters, and it has no status. No status comment exists yet, so this returns and the child PR list is lost. IntegrationDataCreated is sent only when the branches are created, and later messages have no child_prs, so the integration PRs are never shown in the common path. Create the comment here instead of returning, with a neutral state such as comment.title and no result, and add a test that sends IntegrationDataCreated with no existing status comment.

— Claude Code

# informational messages (greetings, help...) do not change the
# state of the pull request
return
existing = 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.

On Bitbucket, PullRequest.comments is cached in _comments and add_comment does not update that cache. If one job calls notify_user twice with a status (e.g. IntegrationDataCreated then BuildNotStarted, once the issue above is fixed), find_status_comment won't see the comment it just created and posts a second one. The mock does not cache, so the tests miss this. Clear or append to _comments after adding the status comment.

— Claude Code

def id(self) -> int:
return self.data['id']

def edit(self, text: str) -> None:

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 new edit methods for GitHub and Bitbucket have no tests. Only the mock and FakeComment are tested. Add a unit test that checks the request (method, URL, payload) and that self.data gets updated.

— Claude Code

@claude

claude Bot commented Oct 2, 2026

Copy link
Copy Markdown
  • Integration PRs are lost when IntegrationDataCreated arrives before any status comment exists (the default auto-creation flow)
    - Create the status comment in that case instead of returning early
    - Add a test that sends IntegrationDataCreated first, with no existing status comment
    - Bitbucket's cached PullRequest.comments is not updated after add_comment, so a second notify_user call in the same job can post a duplicate status comment
    - Clear or append to _comments after creating the status comment
    - Comment.edit for GitHub and Bitbucket has no tests
    - Add unit tests that check the request and the self.data update

    Review by Claude Code

@matthiasL-scality
matthiasL-scality deleted the fix/issue-297 branch October 2, 2026 11:59
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