From 3180c2d0ca7d2711fb11360fd34f7c4ad136f810 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 16:31:03 -0500 Subject: [PATCH 1/9] feat: enhance conflict prediction workflow with PR comments - Modified handle_potential_conflicts.py to output structured data via GitHub Actions outputs - Added PR comment functionality to predict-conflicts.yml workflow - Comments are updateable (sticky) using message-id to prevent duplicates - Shows list of conflicting PRs with links when conflicts are detected - Removes comment when no conflicts exist - Improved error handling and validation in Python script Co-Authored-By: Claude --- .../workflows/handle_potential_conflicts.py | 129 +++++++++++++++--- .github/workflows/predict-conflicts.yml | 28 ++++ 2 files changed, 137 insertions(+), 20 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index ffdae440d85b..34e28ae3db1e 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -19,59 +19,148 @@ """ import sys +import os +import json +import uuid import requests # need to install via pip -import hjson +try: + import hjson +except ImportError: + print("Error: hjson module not found. Please install it with: pip install hjson", file=sys.stderr) + sys.exit(1) def get_pr_json(pr_num): - return requests.get(f'https://api.github.com/repos/dashpay/dash/pulls/{pr_num}').json() + try: + response = requests.get(f'https://api.github.com/repos/dashpay/dash/pulls/{pr_num}') + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"Error fetching PR {pr_num}: {e}", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error parsing JSON for PR {pr_num}: {e}", file=sys.stderr) + sys.exit(1) + +def set_github_output(name, value): + """Set GitHub Actions output""" + if 'GITHUB_OUTPUT' not in os.environ: + print(f"Warning: GITHUB_OUTPUT not set, skipping output: {name}={value}", file=sys.stderr) + return + + try: + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + # For multiline values, use the delimiter syntax + if '\n' in str(value): + delimiter = f"EOF_{uuid.uuid4()}" + f.write(f"{name}<<{delimiter}\n{value}\n{delimiter}\n") + else: + f.write(f"{name}={value}\n") + except IOError as e: + print(f"Error writing to GITHUB_OUTPUT: {e}", file=sys.stderr) def main(): if len(sys.argv) != 2: print(f'Usage: {sys.argv[0]} ', file=sys.stderr) sys.exit(1) - input = sys.argv[1] - print(input) - j_input = hjson.loads(input) - print(j_input) + conflict_input = sys.argv[1] + print(f"Debug: Input received: {conflict_input}", file=sys.stderr) + + try: + j_input = hjson.loads(conflict_input) + except Exception as e: + print(f"Error parsing input JSON: {e}", file=sys.stderr) + sys.exit(1) + + print(f"Debug: Parsed input: {j_input}", file=sys.stderr) + # Validate required fields + if 'pull_number' not in j_input: + print("Error: 'pull_number' field missing from input", file=sys.stderr) + sys.exit(1) + if 'conflictPrs' not in j_input: + print("Error: 'conflictPrs' field missing from input", file=sys.stderr) + sys.exit(1) our_pr_num = j_input['pull_number'] - our_pr_label = get_pr_json(our_pr_num)['head']['label'] - conflictPrs = j_input['conflictPrs'] + our_pr_json = get_pr_json(our_pr_num) + + if 'head' not in our_pr_json or 'label' not in our_pr_json['head']: + print(f"Error: Invalid PR data structure for PR {our_pr_num}", file=sys.stderr) + sys.exit(1) + + our_pr_label = our_pr_json['head']['label'] + conflict_prs = j_input['conflictPrs'] good = [] bad = [] + conflict_details = [] + + for conflict in conflict_prs: + if 'number' not in conflict: + print("Warning: Skipping conflict entry without 'number' field", file=sys.stderr) + continue - for conflict in conflictPrs: conflict_pr_num = conflict['number'] - print(conflict_pr_num) + print(f"Debug: Checking PR #{conflict_pr_num}", file=sys.stderr) conflict_pr_json = get_pr_json(conflict_pr_num) + + if 'head' not in conflict_pr_json or 'label' not in conflict_pr_json['head']: + print(f"Warning: Invalid PR data structure for PR {conflict_pr_num}, skipping", file=sys.stderr) + continue + conflict_pr_label = conflict_pr_json['head']['label'] - print(conflict_pr_label) + print(f"Debug: PR #{conflict_pr_num} label: {conflict_pr_label}", file=sys.stderr) + + if conflict_pr_json.get('mergeable_state') == "dirty": + print(f'PR #{conflict_pr_num} needs rebase. Skipping conflict check', file=sys.stderr) + continue - if conflict_pr_json['mergeable_state'] == "dirty": - print(f'{conflict_pr_num} needs rebase. Skipping conflict check') + if conflict_pr_json.get('draft', False): + print(f'PR #{conflict_pr_num} is a draft. Skipping conflict check', file=sys.stderr) continue - if conflict_pr_json['draft']: - print(f'{conflict_pr_num} is a draft. Skipping conflict check') + try: + pre_mergeable = requests.get(f'https://github.com/dashpay/dash/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}') + pre_mergeable.raise_for_status() + except requests.RequestException as e: + print(f"Error checking mergeability for PR {conflict_pr_num}: {e}", file=sys.stderr) continue - pre_mergeable = requests.get(f'https://github.com/dashpay/dash/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}') if "These branches can be automatically merged." in pre_mergeable.text: good.append(conflict_pr_num) - elif "Can’t automatically merge" in pre_mergeable.text: + elif "Can't automatically merge" in pre_mergeable.text: bad.append(conflict_pr_num) + conflict_details.append({ + 'number': conflict_pr_num, + 'title': conflict_pr_json.get('title', 'Unknown'), + 'url': conflict_pr_json.get('html_url', f'https://github.com/dashpay/dash/pull/{conflict_pr_num}') + }) + else: + print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check. Response snippet: {pre_mergeable.text[:200]}", file=sys.stderr) + + print(f"Not conflicting PRs: {good}", file=sys.stderr) + print(f"Conflicting PRs: {bad}", file=sys.stderr) + + # Set GitHub Actions outputs + if 'GITHUB_OUTPUT' in os.environ: + set_github_output('has_conflicts', 'true' if len(bad) > 0 else 'false') + + # Format conflict details as markdown list + if conflict_details: + markdown_list = [] + for conflict in conflict_details: + markdown_list.append(f"- #{conflict['number']} - [{conflict['title']}]({conflict['url']})") + conflict_markdown = '\n'.join(markdown_list) + set_github_output('conflict_details', conflict_markdown) else: - raise Exception("not mergeable or unmergable!") + set_github_output('conflict_details', '') - print("Not conflicting PRs: ", good) + set_github_output('conflicting_prs', ','.join(map(str, bad))) - print("Conflicting PRs: ", bad) if len(bad) > 0: sys.exit(1) diff --git a/.github/workflows/predict-conflicts.yml b/.github/workflows/predict-conflicts.yml index 186abfd4d1df..b356d12ea019 100644 --- a/.github/workflows/predict-conflicts.yml +++ b/.github/workflows/predict-conflicts.yml @@ -23,10 +23,38 @@ jobs: runs-on: ubuntu-latest steps: - name: check for potential conflicts + id: check_conflicts uses: PastaPastaPasta/potential-conflicts-checker-action@v0.1.10 with: ghToken: "${{ secrets.GITHUB_TOKEN }}" - name: Checkout uses: actions/checkout@v3 - name: validate potential conflicts + id: validate_conflicts run: pip3 install hjson && .github/workflows/handle_potential_conflicts.py "$conflicts" + continue-on-error: true + - name: Post conflict comment + if: steps.validate_conflicts.outputs.has_conflicts == 'true' + uses: mshick/add-pr-comment@v2 + with: + message-id: conflict-prediction + message: | + ## ⚠️ Potential Merge Conflicts Detected + + This PR has potential conflicts with the following open PRs: + + ${{ steps.validate_conflicts.outputs.conflict_details }} + + Please coordinate with the authors of these PRs to avoid merge conflicts. + - name: Remove conflict comment if no conflicts + if: steps.validate_conflicts.outputs.has_conflicts == 'false' + uses: mshick/add-pr-comment@v2 + with: + message-id: conflict-prediction + message: | + ## ✅ No Merge Conflicts Detected + + This PR currently has no conflicts with other open PRs. + - name: Fail if conflicts exist + if: steps.validate_conflicts.outputs.has_conflicts == 'true' + run: exit 1 From 4668cb7cf8fc5b4291de7160d6a17d88751c80f6 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 16:39:54 -0500 Subject: [PATCH 2/9] fix: handle missing PR data gracefully in conflict checker - Changed get_pr_json to return None on API errors instead of exiting - Added proper None checks before accessing PR data - Skip PRs that fail to fetch instead of crashing the entire workflow - This fixes the KeyError when PR API responses don't have expected fields --- .../workflows/handle_potential_conflicts.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index 34e28ae3db1e..6051b8909c1a 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -35,13 +35,20 @@ def get_pr_json(pr_num): try: response = requests.get(f'https://api.github.com/repos/dashpay/dash/pulls/{pr_num}') response.raise_for_status() - return response.json() + pr_data = response.json() + + # Check if we got an error response + if 'message' in pr_data and 'head' not in pr_data: + print(f"Warning: GitHub API error for PR {pr_num}: {pr_data.get('message', 'Unknown error')}", file=sys.stderr) + return None + + return pr_data except requests.RequestException as e: - print(f"Error fetching PR {pr_num}: {e}", file=sys.stderr) - sys.exit(1) + print(f"Warning: Error fetching PR {pr_num}: {e}", file=sys.stderr) + return None except json.JSONDecodeError as e: - print(f"Error parsing JSON for PR {pr_num}: {e}", file=sys.stderr) - sys.exit(1) + print(f"Warning: Error parsing JSON for PR {pr_num}: {e}", file=sys.stderr) + return None def set_github_output(name, value): """Set GitHub Actions output""" @@ -87,6 +94,10 @@ def main(): our_pr_num = j_input['pull_number'] our_pr_json = get_pr_json(our_pr_num) + if our_pr_json is None: + print(f"Error: Failed to fetch PR {our_pr_num}", file=sys.stderr) + sys.exit(1) + if 'head' not in our_pr_json or 'label' not in our_pr_json['head']: print(f"Error: Invalid PR data structure for PR {our_pr_num}", file=sys.stderr) sys.exit(1) @@ -108,6 +119,10 @@ def main(): conflict_pr_json = get_pr_json(conflict_pr_num) + if conflict_pr_json is None: + print(f"Warning: Failed to fetch PR {conflict_pr_num}, skipping", file=sys.stderr) + continue + if 'head' not in conflict_pr_json or 'label' not in conflict_pr_json['head']: print(f"Warning: Invalid PR data structure for PR {conflict_pr_num}, skipping", file=sys.stderr) continue From 75c21e67d26a3a14dfca6082106200eb79502126 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 16:46:18 -0500 Subject: [PATCH 3/9] fix: make repository configurable using GITHUB_REPOSITORY env var - Use GITHUB_REPOSITORY environment variable for API calls - Defaults to dashpay/dash if not set - Fixes testing on forked repositories like PastaPastaPasta/dash --- .github/workflows/handle_potential_conflicts.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index 6051b8909c1a..bd0f5a128175 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -32,8 +32,11 @@ sys.exit(1) def get_pr_json(pr_num): + # Get repository from environment or default to dashpay/dash + repo = os.environ.get('GITHUB_REPOSITORY', 'dashpay/dash') + try: - response = requests.get(f'https://api.github.com/repos/dashpay/dash/pulls/{pr_num}') + response = requests.get(f'https://api.github.com/repos/{repo}/pulls/{pr_num}') response.raise_for_status() pr_data = response.json() @@ -138,8 +141,11 @@ def main(): print(f'PR #{conflict_pr_num} is a draft. Skipping conflict check', file=sys.stderr) continue + # Get repository from environment + repo = os.environ.get('GITHUB_REPOSITORY', 'dashpay/dash') + try: - pre_mergeable = requests.get(f'https://github.com/dashpay/dash/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}') + pre_mergeable = requests.get(f'https://github.com/{repo}/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}') pre_mergeable.raise_for_status() except requests.RequestException as e: print(f"Error checking mergeability for PR {conflict_pr_num}: {e}", file=sys.stderr) From a7be4bba3b85db00c6abd339fe6eb802c67436e5 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 17:00:33 -0500 Subject: [PATCH 4/9] debug: add more detailed logging for merge check failures - Print the exact URL being checked - Show more of the response when unexpected content is received - This will help diagnose why GitHub returns different responses --- .github/workflows/handle_potential_conflicts.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index bd0f5a128175..ac1f81137daa 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -143,9 +143,11 @@ def main(): # Get repository from environment repo = os.environ.get('GITHUB_REPOSITORY', 'dashpay/dash') + merge_check_url = f'https://github.com/{repo}/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}' + print(f"Debug: Checking mergeability at: {merge_check_url}", file=sys.stderr) try: - pre_mergeable = requests.get(f'https://github.com/{repo}/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}') + pre_mergeable = requests.get(merge_check_url) pre_mergeable.raise_for_status() except requests.RequestException as e: print(f"Error checking mergeability for PR {conflict_pr_num}: {e}", file=sys.stderr) @@ -161,7 +163,9 @@ def main(): 'url': conflict_pr_json.get('html_url', f'https://github.com/dashpay/dash/pull/{conflict_pr_num}') }) else: - print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check. Response snippet: {pre_mergeable.text[:200]}", file=sys.stderr) + print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check.", file=sys.stderr) + print(f"URL: {pre_mergeable.url}", file=sys.stderr) + print(f"Response snippet: {pre_mergeable.text[:500]}", file=sys.stderr) print(f"Not conflicting PRs: {good}", file=sys.stderr) print(f"Conflicting PRs: {bad}", file=sys.stderr) From 5a853f2a306bdf28fabfb18ca7004f57cf53120f Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 17:04:32 -0500 Subject: [PATCH 5/9] debug: add more diagnostics for response text analysis - Show total response length - Search for conflict text and show its position - Display context around the conflict message --- .github/workflows/handle_potential_conflicts.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index ac1f81137daa..b186afec96c9 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -165,6 +165,12 @@ def main(): else: print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check.", file=sys.stderr) print(f"URL: {pre_mergeable.url}", file=sys.stderr) + print(f"Response length: {len(pre_mergeable.text)} characters", file=sys.stderr) + # Search for the text we're looking for + if "Can't automatic" in pre_mergeable.text: + idx = pre_mergeable.text.find("Can't automatic") + print(f"Found 'Can't automatic' at position {idx}", file=sys.stderr) + print(f"Context: {pre_mergeable.text[idx-20:idx+100]}", file=sys.stderr) print(f"Response snippet: {pre_mergeable.text[:500]}", file=sys.stderr) print(f"Not conflicting PRs: {good}", file=sys.stderr) From 8b3c43967598048a07e40d2d8c5dfcc166e47d7f Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 17:07:17 -0500 Subject: [PATCH 6/9] fix: improve conflict detection by checking for partial text or X icon - Look for 'Can't automatic' partial text to handle truncated responses - Also check for 'octicon octicon-x' which is the conflict indicator icon - This handles cases where the exact text format varies --- .github/workflows/handle_potential_conflicts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index b186afec96c9..a09502c93f9c 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -155,7 +155,8 @@ def main(): if "These branches can be automatically merged." in pre_mergeable.text: good.append(conflict_pr_num) - elif "Can't automatically merge" in pre_mergeable.text: + elif "Can't automatic" in pre_mergeable.text or "octicon octicon-x" in pre_mergeable.text: + # Check for partial text or the X icon which indicates conflicts bad.append(conflict_pr_num) conflict_details.append({ 'number': conflict_pr_num, From 2758785a2f946816dccdf8056bbc5da5de8a4bec Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 17:09:22 -0500 Subject: [PATCH 7/9] cleanup: remove debug logging now that issues are resolved - Remove verbose debug output for inputs and PR checking - Keep only essential error and warning messages - Streamline output for production use --- .github/workflows/handle_potential_conflicts.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index a09502c93f9c..5cdd6fff40e1 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -76,7 +76,6 @@ def main(): sys.exit(1) conflict_input = sys.argv[1] - print(f"Debug: Input received: {conflict_input}", file=sys.stderr) try: j_input = hjson.loads(conflict_input) @@ -84,8 +83,6 @@ def main(): print(f"Error parsing input JSON: {e}", file=sys.stderr) sys.exit(1) - print(f"Debug: Parsed input: {j_input}", file=sys.stderr) - # Validate required fields if 'pull_number' not in j_input: print("Error: 'pull_number' field missing from input", file=sys.stderr) @@ -118,7 +115,6 @@ def main(): continue conflict_pr_num = conflict['number'] - print(f"Debug: Checking PR #{conflict_pr_num}", file=sys.stderr) conflict_pr_json = get_pr_json(conflict_pr_num) @@ -131,7 +127,6 @@ def main(): continue conflict_pr_label = conflict_pr_json['head']['label'] - print(f"Debug: PR #{conflict_pr_num} label: {conflict_pr_label}", file=sys.stderr) if conflict_pr_json.get('mergeable_state') == "dirty": print(f'PR #{conflict_pr_num} needs rebase. Skipping conflict check', file=sys.stderr) @@ -144,7 +139,6 @@ def main(): # Get repository from environment repo = os.environ.get('GITHUB_REPOSITORY', 'dashpay/dash') merge_check_url = f'https://github.com/{repo}/branches/pre_mergeable/{our_pr_label}...{conflict_pr_label}' - print(f"Debug: Checking mergeability at: {merge_check_url}", file=sys.stderr) try: pre_mergeable = requests.get(merge_check_url) @@ -164,15 +158,7 @@ def main(): 'url': conflict_pr_json.get('html_url', f'https://github.com/dashpay/dash/pull/{conflict_pr_num}') }) else: - print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check.", file=sys.stderr) - print(f"URL: {pre_mergeable.url}", file=sys.stderr) - print(f"Response length: {len(pre_mergeable.text)} characters", file=sys.stderr) - # Search for the text we're looking for - if "Can't automatic" in pre_mergeable.text: - idx = pre_mergeable.text.find("Can't automatic") - print(f"Found 'Can't automatic' at position {idx}", file=sys.stderr) - print(f"Context: {pre_mergeable.text[idx-20:idx+100]}", file=sys.stderr) - print(f"Response snippet: {pre_mergeable.text[:500]}", file=sys.stderr) + print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check. URL: {pre_mergeable.url}", file=sys.stderr) print(f"Not conflicting PRs: {good}", file=sys.stderr) print(f"Conflicting PRs: {bad}", file=sys.stderr) From e0515b9e54b74453066d4b0f334f04203a7cc86c Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 15 Jul 2025 17:23:30 -0500 Subject: [PATCH 8/9] fix: use dynamic repository reference and update checkout action - Use repository from environment variable for conflict PR URLs - Update checkout action from v3 to v4 for latest features --- .github/workflows/handle_potential_conflicts.py | 2 +- .github/workflows/predict-conflicts.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index 5cdd6fff40e1..ec795b301c37 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -155,7 +155,7 @@ def main(): conflict_details.append({ 'number': conflict_pr_num, 'title': conflict_pr_json.get('title', 'Unknown'), - 'url': conflict_pr_json.get('html_url', f'https://github.com/dashpay/dash/pull/{conflict_pr_num}') + 'url': conflict_pr_json.get('html_url', f'https://github.com/{repo}/pull/{conflict_pr_num}') }) else: print(f"Warning: Unexpected response for PR {conflict_pr_num} mergeability check. URL: {pre_mergeable.url}", file=sys.stderr) diff --git a/.github/workflows/predict-conflicts.yml b/.github/workflows/predict-conflicts.yml index b356d12ea019..356ec7fcce68 100644 --- a/.github/workflows/predict-conflicts.yml +++ b/.github/workflows/predict-conflicts.yml @@ -28,7 +28,7 @@ jobs: with: ghToken: "${{ secrets.GITHUB_TOKEN }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: validate potential conflicts id: validate_conflicts run: pip3 install hjson && .github/workflows/handle_potential_conflicts.py "$conflicts" From 538d88d7b4318fabe2f8eca89bee001f7d0bb143 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 16 Jul 2025 16:51:19 -0500 Subject: [PATCH 9/9] fix: add UTF-8 encoding to file open operation This fixes the lint-python-utf8-encoding.py linter failure by explicitly specifying encoding='utf8' when opening the GITHUB_OUTPUT file. --- .github/workflows/handle_potential_conflicts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index ec795b301c37..ada9591d09d8 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -60,7 +60,7 @@ def set_github_output(name, value): return try: - with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf8') as f: # For multiline values, use the delimiter syntax if '\n' in str(value): delimiter = f"EOF_{uuid.uuid4()}"