From e86bacf06c76a1d8a36af29482b6772ffaff78e4 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 13 Jul 2026 04:09:31 -0500 Subject: [PATCH 1/2] ci: move conflict comment state after advisory The hidden state block was rendered first, so raw notification previews of the managed conflict comment started with an opaque base64 blob instead of the advisory text. Render it after the advisory instead. Reading it back can no longer take the first marker in the body, since advisory content (a PR title, say) may contain marker-like text ahead of the real block. Anchor on the last marker instead, unless the body starts with one, which is how comments in the old marker-first layout are still read. --- .../workflows/handle_potential_conflicts.py | 16 ++++++++++++---- .github/workflows/lint.yml | 7 +++++++ .../test_handle_potential_conflicts.py | 19 +++++++++++++++++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index f2c460d00bd3..c564231e8ffd 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -154,7 +154,10 @@ def normalize_state(state: Any) -> dict[str, Any]: def extract_state(body: str) -> dict[str, Any]: - marker_index = body.find(COMMENT_START) + # The state block is rendered last, but comments written before that change + # start with it. Anchor on the last marker unless the body opens with one, + # so marker-like text in advisory content cannot shadow the real block. + marker_index = 0 if body.startswith(COMMENT_START) else body.rfind(COMMENT_START) if marker_index == -1: return normalize_state({}) @@ -218,9 +221,6 @@ def render_comment_body(state: dict[str, Any]) -> str: state_json = json.dumps(state, sort_keys=True, separators=(",", ":")) state_encoded = base64.b64encode(state_json.encode("utf8")).decode("ascii") lines = [ - COMMENT_START, - state_encoded, - COMMENT_END, "## Potential PR merge conflicts", "", "This is advisory only. It does not block CI, but it marks PRs that will likely need a rebase depending on merge order.", @@ -247,6 +247,14 @@ def render_comment_body(state: dict[str, Any]) -> str: inbound_items = sorted(state["inbound"].values(), key=lambda item: int(item["number"])) lines.extend(format_pr_line(item) for item in inbound_items) + # The state block goes last so notification previews start with the advisory text. + lines.extend([ + "", + COMMENT_START, + state_encoded, + COMMENT_END, + ]) + return "\n".join(lines).rstrip() + "\n" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a411130eb42c..11498583d8ea 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,6 +32,13 @@ jobs: git fetch --no-tags -fu origin develop:develop shell: bash + - name: Test workflow scripts + run: | + # The conflict handler and its tests import requests, which the lint container does not ship. + python3 -m pip install --quiet requests + python3 .github/workflows/test_handle_potential_conflicts.py + shell: bash + - name: Run linters run: | export BUILD_TARGET="linux64" diff --git a/.github/workflows/test_handle_potential_conflicts.py b/.github/workflows/test_handle_potential_conflicts.py index 29f12ff7f7ea..55898d7e9666 100755 --- a/.github/workflows/test_handle_potential_conflicts.py +++ b/.github/workflows/test_handle_potential_conflicts.py @@ -91,7 +91,7 @@ def test_rendered_comment_state_round_trips(self): "outbound": [ { "number": 12, - "title": "title with --> marker-like text", + "title": f"title with {handle_potential_conflicts.COMMENT_START} --> marker-like text", "url": "https://github.com/dashpay/dash/pull/12", "files": ["src/net.cpp"], } @@ -103,7 +103,22 @@ def test_rendered_comment_state_round_trips(self): extracted = handle_potential_conflicts.extract_state(body) self.assertEqual(state, extracted) - self.assertNotIn("title with --> marker-like text", body.split("-->")[0]) + self.assertTrue(body.startswith("## Potential PR merge conflicts")) + self.assertTrue(body.rstrip("\n").endswith(handle_potential_conflicts.COMMENT_END)) + + def test_extract_state_from_legacy_marker_first_comment(self): + state = { + "outbound": [ + {"number": 2, "title": "two", "url": "https://example.test/2", "files": []}, + ], + "inbound": {}, + } + body = handle_potential_conflicts.render_comment_body(state) + marker_index = body.rfind(handle_potential_conflicts.COMMENT_START) + legacy_body = body[marker_index:] + body[:marker_index] + + self.assertTrue(legacy_body.startswith(handle_potential_conflicts.COMMENT_START)) + self.assertEqual(state, handle_potential_conflicts.extract_state(legacy_body)) def test_rendered_comment_escapes_markdown_content(self): body = handle_potential_conflicts.render_comment_body({ From 754ffa6dbc78c449d27991d9b665854264555050 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 13 Jul 2026 10:01:15 -0500 Subject: [PATCH 2/2] ci: keep conflict handler tests out of lint --- .github/workflows/lint.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 11498583d8ea..a411130eb42c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,13 +32,6 @@ jobs: git fetch --no-tags -fu origin develop:develop shell: bash - - name: Test workflow scripts - run: | - # The conflict handler and its tests import requests, which the lint container does not ship. - python3 -m pip install --quiet requests - python3 .github/workflows/test_handle_potential_conflicts.py - shell: bash - - name: Run linters run: | export BUILD_TARGET="linux64"