Skip to content

ci(merge-thrash): pr-size-soft-cap advisory workflow (#602) - #603

Merged
robotrocketscience merged 2 commits into
mainfrom
ci/issue-602-pr-size-soft-cap
May 10, 2026
Merged

ci(merge-thrash): pr-size-soft-cap advisory workflow (#602)#603
robotrocketscience merged 2 commits into
mainfrom
ci/issue-602-pr-size-soft-cap

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented May 10, 2026

Copy link
Copy Markdown
Owner

Closes part of #602 (the conflict-probability axis).

What lands

A new advisory GitHub Actions workflow that comments on oversize PRs without blocking them.

  • .github/workflows/pr-size-soft-cap.yml — runs on PR opened / synchronize / reopened / labeled / unlabeled. Skips drafts.
  • Computes loc = additions + deletions and files = changed_files from the PR event payload (no extra API calls for the metrics themselves).
  • Over threshold → posts (or updates) a sticky <!-- pr-size-soft-cap-v1 --> comment.
  • Under threshold → removes any prior sticky comment so a shrunk-back PR doesn't carry a stale warning.
  • size:override label suppresses the comment entirely (legitimate large refactors, module removals, generated code).
  • concurrency.cancel-in-progress: true on a per-PR group so rapid pushes don't race the comment edit.

Thresholds: 200 LOC, 3 files. Both editable in the workflow env: block; living constants, not vendored.

Why advisory not enforcing

The atomic-commits norm already pushes toward small units at the commit level. The merge-thrash data in #602 shows the PR-level aggregate is what determines blast radius — but a hard size block would prevent things like PR #540 (1,747-line module removal) from existing at all. Soft advisory + opt-out label keeps the door open for those while creating useful pressure on the 200–500-line "would-be-fine-as-two-PRs" cohort.

Verification

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/pr-size-soft-cap.yml'))" → loads cleanly.
  • Pinned action SHAs match other workflows in the repo (harden-runner@8d3c67de… v2.19.0).
  • egress-policy: audit matches the house style.
  • Permissions scoped to pull-requests: write + contents: read.
  • Sticky comment idempotency: the MARKER constant in the workflow doubles as the comment-search key. gh api .../comments filtered to github-actions[bot] author + body | startswith(MARKER) finds the existing comment for in-place PATCH or DELETE.

Out of scope (follow-ups)

Discretion

Workflow YAML reviewed for any private terminology — clean. The CHANGELOG entry references #602 by number; nothing about session names or any private context. PR body, ditto.

Summary by Sourcery

Add an advisory GitHub Actions workflow that comments on oversized pull requests to encourage splitting them without blocking merges.

New Features:

  • Introduce a pr-size-soft-cap workflow that posts and updates a sticky advisory comment on PRs exceeding configurable LOC or changed-file thresholds, and removes it when PRs fall back under the limits.

CI:

  • Add a pull_request-triggered workflow that computes PR size from event payload, respects a size:override opt-out label, and uses per-PR concurrency to avoid racing comment updates.

Documentation:

  • Document the new PR-size soft-cap advisory workflow and its behavior in the Unreleased section of the changelog.

Summary by CodeRabbit

Chores

  • Added automated pull request size monitoring that posts advisory comments when changes exceed defined thresholds. Comments are automatically managed and can be bypassed using a label override.

Review Change Stack

@sourcery-ai

sourcery-ai Bot commented May 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new GitHub Actions workflow that posts an advisory sticky comment on oversized PRs based on LOC and file-count thresholds, with opt-out via label and automatic cleanup when PRs shrink, plus a corresponding CHANGELOG entry.

Sequence diagram for pr-size-soft-cap advisory workflow on PR events

sequenceDiagram
  actor Author
  participant GitHub
  participant Workflow as pr-size-soft-cap_workflow
  participant Script as size_check_script
  participant GHAPI as GitHub_API

  Author->>GitHub: Open / update / relabel PR
  GitHub-->>Workflow: pull_request event (opened / synchronize / reopened / labeled / unlabeled)

  alt PR is draft
    Workflow-->>GitHub: Workflow skipped (if draft == true)
  else PR is ready for review
    Workflow->>Script: Run size-check job with PR payload env

    Script->>Script: Check PR_LABELS for size:override
    alt size:override present
      Script-->>GitHub: Exit without comment
    else no override label
      Script->>Script: Compute loc = additions + deletions
      Script->>Script: Read files = changed_files
      Script->>GHAPI: List PR comments (issues/{PR_NUMBER}/comments)
      GHAPI-->>Script: Existing comments
      Script->>Script: Find existing sticky by MARKER and github-actions[bot]

      alt loc <= LOC_LIMIT and files <= FILES_LIMIT
        alt existing sticky found
          Script->>GHAPI: DELETE issues/comments/{existing_id}
          GHAPI-->>Script: Comment deleted
        else no sticky comment
          Script-->>GitHub: Exit (under threshold)
        end
      else over threshold
        Script->>Script: Render comment markdown with MARKER and metrics
        Script->>Script: Build JSON payload
        alt existing sticky found
          Script->>GHAPI: PATCH issues/comments/{existing_id}
          GHAPI-->>Script: Comment updated
        else no sticky comment
          Script->>GHAPI: POST issues/{PR_NUMBER}/comments
          GHAPI-->>Script: Comment created
        end
      end
    end
  end

  Script-->>GitHub: Job complete
Loading

Flow diagram for PR size check and sticky comment management

flowchart TD
  A[Start size-check job] --> B{PR is draft?}
  B -->|Yes| C[Exit workflow]
  B -->|No| D{PR has size:override label?}

  D -->|Yes| E[Log override and exit]
  D -->|No| F[Compute loc = additions + deletions]
  F --> G[Set files = changed_files]
  G --> H[Fetch existing comments via GitHub API]
  H --> I[Locate existing sticky comment by MARKER and author]

  I --> J{loc > LOC_LIMIT or files > FILES_LIMIT?}

  J -->|No| K{Existing sticky comment?}
  K -->|Yes| L[Delete existing sticky comment]
  K -->|No| M[Log under-threshold and exit]
  L --> N[Exit]

  J -->|Yes| O[Render sticky comment markdown with metrics]
  O --> P[Build JSON payload]
  P --> Q{Existing sticky comment?}
  Q -->|Yes| R[PATCH existing comment]
  Q -->|No| S[POST new comment]
  R --> T[Exit]
  S --> U[Exit]
Loading

File-Level Changes

Change Details Files
Introduce a PR-size soft-cap advisory GitHub Actions workflow that comments on oversized PRs and cleans up when they return below thresholds.
  • Add a pull_request-triggered workflow that runs on opened, synchronize, reopened, labeled, and unlabeled events while skipping draft PRs.
  • Compute LOC and changed file counts directly from the pull_request event payload and compare against configurable LOC and file thresholds defined via environment variables.
  • Honor a size:override label to entirely suppress advisory comments for intentionally large changes.
  • Use a sticky HTML marker to find, update, or delete an existing advisory comment authored by github-actions[bot], ensuring idempotent behavior.
  • Configure per-PR-number concurrency with cancel-in-progress to avoid racing comment edits on rapid pushes.
  • Pin the harden-runner action by SHA and set minimal permissions and egress-policy in line with repo standards.
.github/workflows/pr-size-soft-cap.yml
Document the new PR-size soft-cap advisory workflow in the changelog as part of the merge-thrash mitigation work. CHANGELOG.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds a new GitHub Actions workflow that enforces a "soft cap" advisory on pull request size. The workflow monitors LOC changes and file count, posting sticky comments when thresholds are exceeded and cleaning them up when the PR returns below limits. A size:override label bypasses the check entirely.

Changes

PR Size Advisory Workflow

Layer / File(s) Summary
Workflow Trigger and Permissions
.github/workflows/pr-size-soft-cap.yml
Workflow triggers on PR open, synchronize, reopen, labeled, and unlabeled events; grants pull-requests: write permission for comment management.
Concurrency and Job Configuration
.github/workflows/pr-size-soft-cap.yml
Per-PR concurrency cancellation prevents overlapping updates; size-check job runs on Ubuntu with 3-minute timeout and skips draft PRs.
Security Hardening and Environment Setup
.github/workflows/pr-size-soft-cap.yml
Step Security hardened runner applied; environment variables capture PR metadata (number, additions, deletions, file count, labels), advisory thresholds (1000 LOC, 30 files), and fixed comment marker HTML.
Size Evaluation and Comment Management
.github/workflows/pr-size-soft-cap.yml
Core logic checks size:override label and exits if present; computes LOC and file count; deletes sticky comment when under thresholds; posts or updates sticky comment when exceeding either threshold via GitHub API.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically references the main change: introduction of a pr-size-soft-cap advisory workflow for the merge-thrash issue #602.
Description check ✅ Passed The description is comprehensive and covers all key template sections: summary, linked issue, type of change (ci:), verification steps, test plan, and notes for reviewer; it addresses the blocker (heredoc indentation) and nit feedback.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/issue-602-pr-size-soft-cap

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@robotrocketscience robotrocketscience added author-Leibniz PR authored by Leibniz session (don't self-review) attn:review Needs review (PR open, awaiting reviewer) labels May 10, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Einstein:2026-05-10T17:18:28Z]

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Found one blocker, one nit

Blocker — heredoc preserves 10-space indentation

<<EOF (no dash) does not strip leading whitespace from heredoc body lines. Because the cat > /tmp/comment.md <<EOF ... EOF block is itself indented 10 spaces under run: |, every line written to /tmp/comment.md starts with 10 spaces, including the marker.

Verified locally:

$ head -1 /tmp/comment.md | od -c | head -2
0000000                                            <   !   -   -   ...
                  ^^^^^^^^^^^^^^^ 10 spaces

$ jq -Rs '. | startswith("<!-- pr-size-soft-cap-v1 -->")' /tmp/comment.md
false

Two consequences:

  1. Sticky lookup fails. The existing_id jq filter is .body | startswith("${MARKER}"). The body GitHub stores starts with 10 spaces, not the marker, so the filter never matches. Every push posts a fresh comment instead of updating the existing one — the opposite of "sticky".
  2. Markdown renders as a code block. Four-or-more leading spaces on every line tells markdown "this is preformatted text", so the ### PR-size soft cap heading, the bullet list, the bold **N** counts, and the inline \size:override`` references all render inside a code block instead of as formatted markdown.

Fix is one line — make the heredoc tab-stripping (<<-EOF) and use a tab for the leading indent, OR de-indent the body and pipe through sed 's/^ //', OR write the body with printf instead of a heredoc. The <<-EOF variant is the smallest diff but requires the leading whitespace to be tabs, which conflicts with most YAML linters. I'd suggest:

sed 's/^ \{10\}//' > /tmp/comment.md <<EOF
          ${MARKER}
          ### PR-size soft cap
          ...
          EOF

— or just cat <<EOF | sed 's/^ \{10\}//' > /tmp/comment.md. Either way, add a smoke check (grep -c '^<!-- pr-size-soft-cap-v1 -->$' /tmp/comment.md should be 1) so the regression doesn't recur.

Nit — pull_request: ready_for_review

The trigger list is opened, synchronize, reopened, labeled, unlabeled. A PR opened as draft (skipped by the draft == false guard) and later marked ready-for-review won't fire this workflow until the next push or label change. Adding ready_for_review to the trigger list closes that gap. Not a blocker; the next sync push catches it.

Otherwise LGTM

  • harden-runner SHA-pinned ✓
  • set -euo pipefail
  • minimal permissions (contents:read, pull-requests:write) ✓
  • concurrency cancel-in-progress prevents the comment-edit race ✓
  • size:override opt-out matches the issue spec ✓
  • removing the sticky on shrink is a nice touch beyond spec ✓
  • 2 atomic commits, both signed, FF onto main ✓
  • CI green (analyze (python) pending only) ✓

Clearing attn:review while this is awaiting author. Re-flag after the heredoc fix.

@robotrocketscience robotrocketscience removed the attn:review Needs review (PR open, awaiting reviewer) label May 10, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Einstein:2026-05-10T17:21:30Z]

@github-actions

Copy link
Copy Markdown

This PR is now behind main. Rebase locally so your commit signatures stay intact:

git fetch origin && git checkout 'ci/issue-602-pr-size-soft-cap' && git rebase origin/main
# resolve conflicts if any, then
git push --force-with-lease

Auto-rebase was removed because the bot has no signing key; rebasing as the bot strips author signatures and the required_signatures rule on main then blocks the merge. See #341.

@github-actions github-actions Bot added the attn:merge-conflict PR branch needs rebase label May 10, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:godel:2026-05-10T21:44:52Z]

Posts a sticky marker comment on PRs whose additions+deletions > 200
or changed_files > 3, suggesting a split. Quiet under both thresholds
and self-heals (removes the comment if a flagged PR shrinks back below
the line). `size:override` label opts out for legitimate large
diffs (refactors, module removals, generated code).

Addresses the conflict-probability axis of #602's merge-thrash
diagnosis — bigger PRs collide with more open branches and produce
the rebase loops observed on PR #591 (3 force-pushes / 9 label-flips
in 30 min) and #540 (4 force-pushes / 8 label-flips in 9 min). The
serialization axis (label-driven merge-train) ships separately.
@robotrocketscience
robotrocketscience force-pushed the ci/issue-602-pr-size-soft-cap branch from c27ac86 to 8179bbb Compare May 10, 2026 21:47
@robotrocketscience robotrocketscience removed the attn:merge-conflict PR branch needs rebase label May 10, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Rebased onto current main (post-#568 merge). Both commits re-signed. Local pytest 3286 passed / 52 skipped. Diff vs main is now 2 files: .github/workflows/pr-size-soft-cap.yml (+105) and CHANGELOG.md (+4). Awaiting required-status checks before FF-merge.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/pr-size-soft-cap.yml (1)

12-12: ⚡ Quick win

Add ready_for_review to trigger on draft→ready transitions.

The workflow currently only runs on opened, synchronize, reopened, labeled, and unlabeled events. Adding ready_for_review ensures the workflow also executes when a PR transitions from draft to ready status.

Suggested change
-    types: [opened, synchronize, reopened, labeled, unlabeled]
+    types: [opened, synchronize, reopened, labeled, unlabeled, ready_for_review]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-size-soft-cap.yml at line 12, The workflow's event
types array (the line containing types: [opened, synchronize, reopened, labeled,
unlabeled]) is missing the ready_for_review event; update that array to include
ready_for_review so the job also triggers when a PR transitions from draft to
ready (e.g., add ready_for_review to the list alongside the other event names).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-size-soft-cap.yml:
- Around line 51-67: The early exit when the "size:override" label is present
skips removing any prior sticky advisory comment; change the opt-out branch so
it still looks up and removes an existing sticky comment before exiting: keep
the label check for PR_LABELS, but if present run the same lookup that assigns
existing_id (using MARKER and the gh api query that filters github-actions[bot]
comments), and if existing_id is non-empty call gh api to delete
"repos/${REPO}/issues/${PR_NUMBER}/comments/${existing_id}" (or the equivalent
gh api delete command) and only then echo the opt-out message and exit;
reference the PR_LABELS check, existing_id variable, MARKER and the gh api
comments lookup/delete logic to implement this.

---

Nitpick comments:
In @.github/workflows/pr-size-soft-cap.yml:
- Line 12: The workflow's event types array (the line containing types: [opened,
synchronize, reopened, labeled, unlabeled]) is missing the ready_for_review
event; update that array to include ready_for_review so the job also triggers
when a PR transitions from draft to ready (e.g., add ready_for_review to the
list alongside the other event names).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af01e15a-5882-43b9-8ef5-24d4fb4851c2

📥 Commits

Reviewing files that changed from the base of the PR and between 2c6a71a and 8179bbb.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/CHANGELOG.md
📒 Files selected for processing (1)
  • .github/workflows/pr-size-soft-cap.yml

Comment on lines +51 to +67
# Opt-out via label.
if printf '%s' ",${PR_LABELS}," | grep -q ',size:override,'; then
echo "size:override label present; nothing to do."
exit 0
fi

loc=$((PR_ADDITIONS + PR_DELETIONS))
files="${PR_CHANGED_FILES}"
over=0
if [ "$loc" -gt "$LOC_LIMIT" ]; then over=1; fi
if [ "$files" -gt "$FILES_LIMIT" ]; then over=1; fi

# Find any prior sticky comment authored by github-actions[bot].
existing_id="$(gh api \
"repos/${REPO}/issues/${PR_NUMBER}/comments" --paginate \
--jq "[.[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"${MARKER}\")))] | .[0].id // empty")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

size:override exits before stale sticky cleanup.

At Line 52, the script exits before resolving/deleting any existing sticky comment, so previously posted advisories can remain visible after opt-out (and contradict the message at Line 92).

💡 Suggested fix
-          # Opt-out via label.
-          if printf '%s' ",${PR_LABELS}," | grep -q ',size:override,'; then
-            echo "size:override label present; nothing to do."
-            exit 0
-          fi
-
           loc=$((PR_ADDITIONS + PR_DELETIONS))
           files="${PR_CHANGED_FILES}"
           over=0
           if [ "$loc" -gt "$LOC_LIMIT" ]; then over=1; fi
           if [ "$files" -gt "$FILES_LIMIT" ]; then over=1; fi

           # Find any prior sticky comment authored by github-actions[bot].
           existing_id="$(gh api \
               "repos/${REPO}/issues/${PR_NUMBER}/comments" --paginate \
               --jq "[.[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"${MARKER}\")))] | .[0].id // empty")"
+
+          # Opt-out via label.
+          if printf '%s' ",${PR_LABELS}," | grep -q ',size:override,'; then
+            if [ -n "$existing_id" ]; then
+              echo "size:override label present; deleting sticky comment ${existing_id}."
+              gh api -X DELETE "repos/${REPO}/issues/comments/${existing_id}" >/dev/null
+            else
+              echo "size:override label present; nothing to do."
+            fi
+            exit 0
+          fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-size-soft-cap.yml around lines 51 - 67, The early exit
when the "size:override" label is present skips removing any prior sticky
advisory comment; change the opt-out branch so it still looks up and removes an
existing sticky comment before exiting: keep the label check for PR_LABELS, but
if present run the same lookup that assigns existing_id (using MARKER and the gh
api query that filters github-actions[bot] comments), and if existing_id is
non-empty call gh api to delete
"repos/${REPO}/issues/${PR_NUMBER}/comments/${existing_id}" (or the equivalent
gh api delete command) and only then echo the opt-out message and exit;
reference the PR_LABELS check, existing_id variable, MARKER and the gh api
comments lookup/delete logic to implement this.

@robotrocketscience
robotrocketscience merged commit 8179bbb into main May 10, 2026
30 of 31 checks passed
@robotrocketscience
robotrocketscience deleted the ci/issue-602-pr-size-soft-cap branch May 10, 2026 22:59
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:godel:2026-05-10T22:59:47Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author-Leibniz PR authored by Leibniz session (don't self-review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant