From 26aab48ca50a3f0fc17b6efddf88e14b5390dbc3 Mon Sep 17 00:00:00 2001 From: james-haytko_nwx Date: Tue, 24 Feb 2026 12:36:00 -0600 Subject: [PATCH 1/2] Fix YAML heredoc break from unindented FOOTER lines The triple-quoted Python string for FOOTER had lines at column 0, which caused YAML to truncate the run block early and bash could never find the PYTHON_EOF terminator. Replaced with string concatenation so every line stays inside the YAML indent level. Generated with AI Co-Authored-By: Claude Code --- .../claude-documentation-reviewer.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/claude-documentation-reviewer.yml b/.github/workflows/claude-documentation-reviewer.yml index 0a7e5b8704..aa51b83b8c 100644 --- a/.github/workflows/claude-documentation-reviewer.yml +++ b/.github/workflows/claude-documentation-reviewer.yml @@ -121,14 +121,17 @@ jobs: import os import sys - FOOTER = """ ---- - -There are two ways to apply fixes: -- View them in the comments and apply them individually or in a batch. This only applies to changes made to the file. -- Reply with `@claude` here, followed by your instructions (e.g. `@claude fix all issues` or `@claude fix only the spelling errors` or `@claude fix all other existing issues`). You can use this option to fix preexisting issues. - -Note: Automated fixes are only available for branches in this repository, not forks.""" + FOOTER = ( + "\n---\n\n" + "There are two ways to apply fixes:\n" + "- View them in the comments and apply them individually or in a batch." + " This only applies to changes made to the file.\n" + "- Reply with `@claude` here, followed by your instructions" + " (e.g. `@claude fix all issues` or `@claude fix only the spelling errors`" + " or `@claude fix all other existing issues`)." + " You can use this option to fix preexisting issues.\n\n" + "Note: Automated fixes are only available for branches in this repository, not forks." + ) def parse_diff_to_suggestions(diff_text): suggestions = [] From 2c32ea21183bfe7ecdb3d65ef5a1dd2d15713667 Mon Sep 17 00:00:00 2001 From: james-haytko_nwx Date: Tue, 24 Feb 2026 13:07:59 -0600 Subject: [PATCH 2/2] Filter inline suggestions to PR diff lines only When reviewing the full document, Claude fixes preexisting issues on lines outside the PR diff. GitHub rejects the entire review with HTTP 422 if any suggestion references a line not in the diff. Fix: parse the PR diff (with --unified=10 context) to build the set of valid (file, line) pairs before posting. Suggestions for out-of-diff lines are skipped; they still appear in the prose summary and can be applied via @claude. Generated with AI Co-Authored-By: Claude Code --- .../claude-documentation-reviewer.yml | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-documentation-reviewer.yml b/.github/workflows/claude-documentation-reviewer.yml index aa51b83b8c..29fb374ec4 100644 --- a/.github/workflows/claude-documentation-reviewer.yml +++ b/.github/workflows/claude-documentation-reviewer.yml @@ -111,6 +111,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} REPO: ${{ github.repository }} run: | @@ -220,6 +221,33 @@ jobs: comment['start_side'] = 'RIGHT' return comment + def get_pr_diff_valid_lines(base_sha, head_sha): + """Return the set of (file, line_number) in HEAD visible in the PR diff.""" + result = subprocess.run( + ['git', 'diff', '--unified=10', base_sha, head_sha], + capture_output=True, text=True, + ) + valid = set() + current_file = None + new_line_num = 0 + for line in result.stdout.split('\n'): + if line.startswith('+++ b/'): + current_file = line[6:] + elif line.startswith('@@'): + match = re.match(r'@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@', line) + if match: + new_line_num = int(match.group(1)) + elif line.startswith('+') and not line.startswith('+++'): + if current_file: + valid.add((current_file, new_line_num)) + new_line_num += 1 + elif line.startswith(' '): + if current_file: + valid.add((current_file, new_line_num)) + new_line_num += 1 + # '-' lines don't exist in HEAD, skip + return valid + # Read the review summary Claude wrote summary_path = '/tmp/review-summary.md' if os.path.exists(summary_path): @@ -230,14 +258,28 @@ jobs: review_body += FOOTER + pr_number = os.environ['PR_NUMBER'] + head_sha = os.environ['HEAD_SHA'] + base_sha = os.environ['BASE_SHA'] + repo = os.environ['REPO'] + # Get diff of Claude's local edits vs HEAD result = subprocess.run(['git', 'diff', 'HEAD'], capture_output=True, text=True) diff_text = result.stdout - suggestions = parse_diff_to_suggestions(diff_text) if diff_text.strip() else [] + all_suggestions = parse_diff_to_suggestions(diff_text) if diff_text.strip() else [] - pr_number = os.environ['PR_NUMBER'] - head_sha = os.environ['HEAD_SHA'] - repo = os.environ['REPO'] + # Filter to only lines visible in the PR diff — GitHub rejects suggestions + # on lines outside the diff context with HTTP 422. + pr_valid_lines = get_pr_diff_valid_lines(base_sha, head_sha) + suggestions = [] + for s in all_suggestions: + start = s.get('start_line', s['line']) + end = s['line'] + if all((s['path'], ln) in pr_valid_lines for ln in range(start, end + 1)): + suggestions.append(s) + else: + print(f"Skipping out-of-diff suggestion: {s['path']} line {s['line']}") + print(f"{len(suggestions)}/{len(all_suggestions)} suggestions are within the PR diff.") review_payload = { 'commit_id': head_sha,