From 03cde3e8241ab19bededeffbf6b82ca677f4f68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 00:36:40 +0200 Subject: [PATCH 1/7] Use python:3-slim base image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 488b47b..9279033 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Multi-stage build for optimal image size -FROM python:3.11-slim AS builder +FROM python:3-slim AS builder WORKDIR /build @@ -8,7 +8,7 @@ COPY action/src/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Final stage -FROM python:3.11-slim +FROM python:3-slim WORKDIR /action From 3067540dcc0052f70e48b8ae4fbc6ac409fdeb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 00:42:52 +0200 Subject: [PATCH 2/7] Better validation logging --- action/src/changelog_generator.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/action/src/changelog_generator.py b/action/src/changelog_generator.py index 4f6de1d..dc477d6 100644 --- a/action/src/changelog_generator.py +++ b/action/src/changelog_generator.py @@ -394,6 +394,9 @@ def generate_with_validation( else: return None, False, "Failed to generate valid YAML after retries" + # Log the generated entry for debugging + logger.debug(f"Generated entry (attempt {attempt}):\n{generated_entry}") + # Validate the generated entry is_valid, validation_errors = validator.validate(generated_entry) @@ -408,11 +411,17 @@ def generate_with_validation( # Entry invalid - log errors and retry if attempts remain error_message = "; ".join(validation_errors) logger.warning(f"Validation failed on attempt {attempt}: {error_message}") + logger.warning( + f"Generated YAML that failed validation (attempt {attempt}):\n{generated_entry}" + ) if attempt <= max_retries: logger.info( f"Retrying with validation feedback... (attempt {attempt + 1})" ) + logger.info( + f"Validation feedback for retry: {'; '.join(validation_errors)}" + ) continue else: # All retries exhausted @@ -453,14 +462,14 @@ def _build_retry_prompt( if original_prompt: # If there was a custom prompt, append retry context to it - return f"{original_prompt}\n\n{retry_context}" + retry_prompt = f"{original_prompt}\n\n{retry_context}" else: # Otherwise, build a complete prompt with the diff and retry context pr_title = pr_info.get("title", "") pr_body = pr_info.get("body", "") pr_author = pr_info.get("user", {}).get("login", "unknown") - return f"""Generate a logchange-formatted YAML changelog entry for this PR. + retry_prompt = f"""Generate a logchange-formatted YAML changelog entry for this PR. PR Title: {pr_title} @@ -478,6 +487,9 @@ def _build_retry_prompt( Output ONLY the corrected YAML with no additional text.""" + logger.debug(f"Retry prompt:\n{retry_prompt}") + return retry_prompt + def _extract_yaml(self, text: str) -> str: """Extract YAML from markdown code blocks if present""" # Try to extract from ```yaml ... ``` or ``` ... ``` block From b62f6a3924dc35da608031c65500348be6685a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 00:43:59 +0200 Subject: [PATCH 3/7] Better log msg when unavailable --- action/src/main.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/action/src/main.py b/action/src/main.py index 0ee8d9d..dab3986 100644 --- a/action/src/main.py +++ b/action/src/main.py @@ -395,7 +395,8 @@ def _generate_and_validate( if not self.generator: logger.warning(f"Generator not available for {context_name}") self.github_client.comment_on_pr( - f"⚠️ {context_name} generation requested but generator not available" + "⚠️ Changelog generation is currently unavailable.\n\n" + "Please try again with your next commit." ) self.set_output("generation-error", "Generator not available") return None @@ -420,8 +421,8 @@ def _generate_and_validate( f"{context_name} generation failed: {validation_message}" ) self.github_client.comment_on_pr( - f"⚠️ {context_name} generation failed\n\n" - f"*(Reason: {validation_message})*" + "⚠️ Changelog generation encountered some issues.\n\n" + "No worries! Please try again with your next commit." ) self.set_output("generation-error", validation_message) return None @@ -429,7 +430,8 @@ def _generate_and_validate( if not is_valid: logger.error(f"Generated {context_name} invalid: {validation_message}") self.github_client.comment_on_pr( - f"⚠️ Generated {context_name} failed validation" + "⚠️ Changelog generation encountered some issues.\n\n" + "No worries! Please try again with your next commit." ) self.set_output("generation-error", "Validation failed") return None @@ -445,7 +447,8 @@ def _generate_and_validate( except GenerationError as e: logger.warning(f"{context_name} generation error: {e}") self.github_client.comment_on_pr( - f"⚠️ {context_name} generation failed: {str(e)}" + "⚠️ Changelog generation encountered some issues.\n\n" + "No worries! Please try again with your next commit." ) self.set_output("generation-error", str(e)) return None From 1b003d80e5dad8a7b119c3a0bd3319976de31682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 00:49:45 +0200 Subject: [PATCH 4/7] Legacy changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55a9c63..63ca28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Support for developers who refuse to write changelog entries (tough love) (#298) (Nina Gupta) - Legacy "I forgot the changelog" excuse handling (#445) (Raj Patel) +- Irrelevant multi-line entry that just discusses elephants and other completely + unrelated topics to the actual change in the PR. Let's see how Claude handles + this then (#111) (Jon Blow, Jane Snow) ### Security - Added detection for when developers try to hide breaking changes in the changelog (#567) (Lisa Wong) From 2942e1c2dbbdaa1692fa18b165663ad8ea6541b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 00:57:27 +0200 Subject: [PATCH 5/7] Existing comment check earlier --- action/src/main.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/action/src/main.py b/action/src/main.py index dab3986..59e1ed9 100644 --- a/action/src/main.py +++ b/action/src/main.py @@ -203,6 +203,19 @@ def run(self) -> int: logger.info("Starting logchange action") + # Early exit: if generation is enabled, check for existing suggestion before + # doing expensive file operations + if ( + self.on_missing_entry == "generate" + and self._has_existing_suggestion() + ): + logger.info( + "Changelog suggestion already exists on this PR, skipping all operations" + ) + self.set_output("changelog-found", "false") + self.set_output("changelog-generated", "false") + return 0 + # Get PR files pr_files = self.github_client.get_pr_files() logger.info(f"Found {len(pr_files)} files in PR") @@ -380,7 +393,6 @@ def _generate_and_validate( - Calling generate_with_validation - Checking for generation failures - Checking for validation failures - - Checking for duplicate suggestions - Posting error comments - Setting error outputs @@ -436,11 +448,6 @@ def _generate_and_validate( self.set_output("generation-error", "Validation failed") return None - # Check for duplicate suggestion - if self._has_existing_suggestion(): - logger.info(f"{context_name} suggestion already exists, skipping") - return None - logger.info(f"{context_name} generated and validated successfully") return generated_entry From 9147832535ed0f2f4ff4ea6a243c959c629aac1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 01:10:10 +0200 Subject: [PATCH 6/7] Better PR log comments --- action/src/github_client.py | 12 ++++++++- action/src/main.py | 49 ++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/action/src/github_client.py b/action/src/github_client.py index f8a7482..a69132e 100644 --- a/action/src/github_client.py +++ b/action/src/github_client.py @@ -264,17 +264,20 @@ def create_review_comment_with_suggestion( line: int, body: str, suggestion: str = "", + start_line: int = None, ) -> bool: """ Create a review comment on a specific line with optional suggested changes. + Supports multi-line suggestions. Args: commit_sha: The commit SHA where the comment should be posted file_path: Path to the file in the PR - line: Line number (in the new version of the file) + line: Line number (in the new version of the file, or end line for multi-line) body: The comment text (markdown format with suggestion syntax if applicable) suggestion: Optional suggested replacement text (for "suggest edits" feature). Use "```suggestion\n\n```" format in body instead. + start_line: Optional start line for multi-line comments (if None, single line comment) Returns: True if successful, False otherwise @@ -292,6 +295,10 @@ def create_review_comment_with_suggestion( "body": body, } + # Add start_line for multi-line comments + if start_line is not None: + comment_data["start_line"] = start_line + payload = { "body": "Review with suggested changes", "event": "COMMENT", @@ -308,4 +315,7 @@ def create_review_comment_with_suggestion( except requests.exceptions.RequestException as e: logger.error(f"Failed to create review comment: {e}") + # Log response content for debugging 422 errors + if hasattr(e.response, 'text'): + logger.debug(f"GitHub API response: {e.response.text}") return False diff --git a/action/src/main.py b/action/src/main.py index 59e1ed9..8515158 100644 --- a/action/src/main.py +++ b/action/src/main.py @@ -657,19 +657,34 @@ def _convert_legacy_to_logchange( ) for start_line, end_line, group_content in line_groups: - # Format the suggestion with markdown syntax for removal - suggestion_body = ( - "This was converted to logchange format. Let's remove it.\n\n" - "```suggestion\n" - "```" - ) - - self.github_client.create_review_comment_with_suggestion( - commit_sha=commit_sha, - file_path=legacy_file, - line=end_line, # Post on the last line of the group - body=suggestion_body, - ) + is_single_line = start_line == end_line + + if is_single_line: + # For single-line removals, use GitHub's suggestion syntax + suggestion_body = ( + "This was converted to logchange format. Let's remove it.\n\n" + "```suggestion\n" + "```" + ) + self.github_client.create_review_comment_with_suggestion( + commit_sha=commit_sha, + file_path=legacy_file, + line=end_line, + body=suggestion_body, + ) + else: + # For multi-line removals, post a regular comment on the last line + # (GitHub's multi-line suggestion API has limitations) + suggestion_body = ( + f"Lines {start_line}-{end_line}: This was converted to logchange format. " + "Please remove these lines." + ) + self.github_client.create_review_comment_with_suggestion( + commit_sha=commit_sha, + file_path=legacy_file, + line=end_line, + body=suggestion_body, + ) # Post the converted entry as a regular comment suggestion_comment = self._format_legacy_conversion_comment( @@ -701,9 +716,9 @@ def _format_legacy_conversion_comment( filename = generate_changelog_slug(pr_number, pr_title) file_path = f"{self.changelog_path}/{filename}" - return f"""🔄 **I've converted the legacy changelog entry to logchange format!** + return f"""🔄 **I've converted the changelog entry to logchange format!** -I detected a change to `{legacy_file}` and converted it to the logchange format below. +I detected changes to `{legacy_file}` and converted them to the logchange format below. **Suggested Logchange Entry** for `{file_path}`: @@ -717,7 +732,7 @@ def _format_legacy_conversion_comment( - Create a new file at `{file_path}` - Copy the YAML above into it -2. ⚠️ **Remove** the legacy changes: +2. ⚠️ **Remove** the original entry: - I've added suggested edits to remove the lines you added to `{legacy_file}` - Review the suggestions in the PR review and accept them @@ -727,7 +742,7 @@ def _format_legacy_conversion_comment( - Feel free to edit the logchange YAML **Why?** -This project uses logchange format for changelog entries, not the traditional {legacy_file} format. Using logchange ensures consistency and better tooling support. +This project uses logchange format for changelog entries. Using logchange ensures consistency and better tooling support across all changelog entries. """ From abf14119534a728148ab3e71264a8cc8cd1323f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Sun, 26 Oct 2025 01:14:58 +0200 Subject: [PATCH 7/7] Fix multi line review comments --- action/src/github_client.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/action/src/github_client.py b/action/src/github_client.py index a69132e..ff26576 100644 --- a/action/src/github_client.py +++ b/action/src/github_client.py @@ -268,7 +268,7 @@ def create_review_comment_with_suggestion( ) -> bool: """ Create a review comment on a specific line with optional suggested changes. - Supports multi-line suggestions. + Supports multi-line comments. Args: commit_sha: The commit SHA where the comment should be posted @@ -286,27 +286,24 @@ def create_review_comment_with_suggestion( logger.warning("No PR number found") return False - url = f"{self.api_url}/repos/{self.repo_owner}/{self.repo_name}/pulls/{self.pr_number}/reviews" + url = f"{self.api_url}/repos/{self.repo_owner}/{self.repo_name}/pulls/{self.pr_number}/comments" # Build the comment payload comment_data = { + "body": body, + "commit_id": commit_sha, "path": file_path, "line": line, - "body": body, + "side": "RIGHT", # Comment on the new version of the file } - # Add start_line for multi-line comments + # Add start_line and start_side for multi-line comments if start_line is not None: comment_data["start_line"] = start_line - - payload = { - "body": "Review with suggested changes", - "event": "COMMENT", - "comments": [comment_data], - } + comment_data["start_side"] = "RIGHT" try: - response = self.session.post(url, json=payload) + response = self.session.post(url, json=comment_data) response.raise_for_status() logger.info( f"Successfully created review comment with suggestion on line {line}" @@ -315,7 +312,7 @@ def create_review_comment_with_suggestion( except requests.exceptions.RequestException as e: logger.error(f"Failed to create review comment: {e}") - # Log response content for debugging 422 errors + # Log response content for debugging errors if hasattr(e.response, 'text'): logger.debug(f"GitHub API response: {e.response.text}") return False