Skip to content

Add pre-PR checks to cut SonarCloud and Sourcery review rounds - #95

Merged
codegeek merged 2 commits into
mainfrom
docs/pre-pr-checks
Oct 22, 2026
Merged

codegeek merged 2 commits into
mainfrom
docs/pre-pr-checks

Conversation

@codegeek

@codegeek codegeek commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Most PRs have needed several push-and-review rounds: SonarCloud raised 132 issues across 34 rules on merged PRs #28–#94, and Sourcery opened 133 review threads on #36–#94. Many fall into recurring patterns that can be caught before the PR exists. This adds a pre-PR step so each PR needs at most one follow-up round.

Changes

  • AGENTS.md: new "Before opening a PR" section.
    • Check the diff against the review checklist.
    • Run a local Sonar analysis: ./gradlew build sonar --no-parallel -Dsonar.branch.name=<branch> uploads a short-lived branch analysis with the PR gate's rules, then read the branch's open issues. The SonarQube CLI's sonar analyze can't be used for this: its code analysis (Vortex) returns 403 on this plan, so it only scans secrets.
    • Have an agent from the other vendor review the diff against the same checklist.
    • Push once per review round, after both SonarCloud and Sourcery have reported.
  • .github/REVIEW_CHECKLIST.md: built from every merged PR's Sonar issues and Sourcery threads.
    • The Sonar rules this repo keeps hitting, most frequent first, with the fix for each (S7467, S1186, S5778, S2925, S3077, …).
    • 14 groups of Sourcery findings with example PRs: failure and cleanup paths, concurrency and reload, protocol size limits, hostile input, extension boundaries, validation edge cases, rejected requests, counters, security, determinism, persistence, API compatibility, weak tests and test reliability, and docs consistency.
  • .gitignore: ignore the .sonar/ cache the scanner leaves behind.

The project is unreleased, so the checklist suggests stating intentional public API breaks in the PR body rather than keeping old constructors.

Testing

  • Ran ./gradlew build sonar --no-parallel -Dsonar.branch.name=docs/pre-pr-checks locally: build passed, and SonarCloud lists docs/pre-pr-checks as a short-lived branch with the gate OK and 0 issues.
  • Docs-only change; no code affected.

🤖 Generated with Claude Code

Summary by Sourcery

Introduce pre-PR quality checks to catch recurring SonarCloud and Sourcery findings before pull requests are opened.

New Features:

  • Add a pre-PR review process covering recurring SonarCloud and Sourcery findings.

Enhancements:

  • Document a consolidated review checklist for common code quality, correctness, security, concurrency, protocol, testing, API, and documentation issues.
  • Document local SonarCloud branch analysis and independent review steps before opening a pull request.

Documentation:

  • Add contributor guidance for pre-PR checks, SonarCloud analysis, cross-vendor review, and batching fixes between review rounds.

Chores:

  • Ignore the Sonar scanner cache directory.

Most PRs have needed several push-and-review rounds for findings that
could have been caught before the PR existed. Add a "Before opening a
PR" section to AGENTS.md and a review checklist built from the Sonar
issues and Sourcery threads on every merged PR (#28-#94): the Sonar
rules this repo keeps hitting and the kinds of defects Sourcery keeps
finding.

The SonarQube CLI's code analysis returns 403 on this plan, so the
local Sonar step uses the Gradle scanner's branch analysis instead.
Ignore the .sonar/ cache it leaves behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This documentation-only PR introduces a repeatable pre-PR quality workflow: contributors use a repository-derived SonarCloud/Sourcery checklist, run a local short-lived SonarCloud analysis, obtain an independent cross-vendor review, and batch fixes before pushing. It also ignores the scanner cache generated by that workflow.

Flow diagram for the pre-PR quality workflow

flowchart LR
    Change["Prepare change"] --> Checklist["Review diff against checklist"]
    Checklist --> Sonar["Run local SonarCloud branch analysis"]
    Sonar --> Issues["Read and fix open SonarCloud issues"]
    Issues --> Review["Obtain independent cross-vendor review"]
    Review --> Fixes["Fix real findings"]
    Fixes --> Push["Push one batched review round"]
Loading

File-Level Changes

Change Details Files
Added a repository-specific checklist for recurring SonarCloud and Sourcery findings, organized into static-analysis rules and behavioral review categories.
  • Documented common SonarCloud rules and preferred fixes for production and test code.
  • Captured recurring Sourcery concerns covering cleanup, concurrency, protocol limits, hostile input, validation, security, persistence, API compatibility, test quality, and documentation.
  • Included historical PR references to provide review context.
.github/REVIEW_CHECKLIST.md
Established a mandatory pre-PR workflow that combines checklist review, local SonarCloud analysis, independent agent review, and batched review-round pushes.
  • Added instructions for running Gradle build and SonarCloud short-lived branch analysis with the required branch and token setup.
  • Directed contributors to inspect and fix open SonarCloud issues before opening a PR.
  • Added cross-vendor agent review against the shared checklist and guidance to batch fixes before pushing.
AGENTS.md
Excluded SonarCloud scanner cache artifacts from version control.
  • Added the scanner cache directory to Git ignore rules.
.gitignore

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

@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 found 7 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="AGENTS.md" line_range="32" />
<code_context>
+- Local Sonar analysis: `./gradlew build sonar --no-parallel -Dsonar.branch.name=<branch>` (`--no-parallel` as in CI). It uploads a short-lived branch analysis to SonarCloud with the PR gate's rules and needs a token in `systemProp.sonar.token` in `~/.gradle/gradle.properties` (never read or print that file). Then read the branch's open issues (`https://sonarcloud.io/api/issues/search?componentKeys=jircd_jircd-server&branch=<branch>&resolved=false`) and fix them before opening the PR. The SonarQube CLI's `sonar analyze` only scans secrets on this plan; its code analysis returns 403.
</code_context>
<issue_to_address>
**Local scan exposes Sonar token**

When a maintainer runs the documented scan on a branch containing untrusted Gradle build logic while the Sonar token is configured, gradle executes the checked-out project’s build logic while `build sonar` runs; malicious build logic can read the configured `systemProp.sonar.token` and send it off-host, exposing the token and its SonarCloud permissions.

Warn against running the token-bearing scan on untrusted code, and use an isolated, least-privileged token only for trusted branches.
</issue_to_address>

### Comment 2
<location path="AGENTS.md" line_range="32" />
<code_context>
+- Local Sonar analysis: `./gradlew build sonar --no-parallel -Dsonar.branch.name=<branch>` (`--no-parallel` as in CI). It uploads a short-lived branch analysis to SonarCloud with the PR gate's rules and needs a token in `systemProp.sonar.token` in `~/.gradle/gradle.properties` (never read or print that file). Then read the branch's open issues (`https://sonarcloud.io/api/issues/search?componentKeys=jircd_jircd-server&branch=<branch>&resolved=false`) and fix them before opening the PR. The SonarQube CLI's `sonar analyze` only scans secrets on this plan; its code analysis returns 403.
</code_context>
<issue_to_address>
**Branch name executes shell commands**

When a maintainer substitutes an attacker-controlled Git branch name containing shell metacharacters for `<branch>` and runs the documented command, the shell expands command substitutions in the unquoted `-Dsonar.branch.name=<branch>` argument before Gradle starts, so a malicious branch name can execute arbitrary commands as the maintainer.

Pass the branch name as data through a safely expanded shell variable instead of interpolating it into the command text.
</issue_to_address>

### Comment 3
<location path=".github/REVIEW_CHECKLIST.md" line_range="48" />
<code_context>
+- **Validation edge cases.** Treat empty, null, negative, zero, duplicate and out-of-range values explicitly: an empty parameter, an explicit `null` YAML section, a null bind address (which must not mean "all interfaces"), a negative limit, a duplicate CAP offer, an empty CAP REQ. Validate every configuration source, including reload and constructors. [#37, #39, #61, #67, #79, #80, #87]
</code_context>
<issue_to_address>
**Null bind-address guidance contradicts the API**

When a contributor applies this checklist item to the public listener API, contributors applying this guidance to `ServerConfiguration.Listener` treat a null `bindAddress` as invalid or non-wildcard, contradicting its documented contract and changing which interfaces the listener binds.

Clarify that an explicitly null YAML value is rejected, while a null `Listener.bindAddress` retains its documented wildcard meaning.
</issue_to_address>

### Comment 4
<location path="AGENTS.md" line_range="32" />
<code_context>
+- Local Sonar analysis: `./gradlew build sonar --no-parallel -Dsonar.branch.name=<branch>` (`--no-parallel` as in CI). It uploads a short-lived branch analysis to SonarCloud with the PR gate's rules and needs a token in `systemProp.sonar.token` in `~/.gradle/gradle.properties` (never read or print that file). Then read the branch's open issues (`https://sonarcloud.io/api/issues/search?componentKeys=jircd_jircd-server&branch=<branch>&resolved=false`) and fix them before opening the PR. The SonarQube CLI's `sonar analyze` only scans secrets on this plan; its code analysis returns 403.
</code_context>
<issue_to_address>
**Sonar findings are missed**

When the branch has more than 100 open Sonar issues, the issues-search request returns only its first 100 results, so later findings are omitted and unresolved issues reach the PR.

Request and inspect every page of the issues-search results before treating the review as complete.
</issue_to_address>

### Comment 5
<location path=".github/REVIEW_CHECKLIST.md" line_range="3" />
<code_context>
+# Pre-PR review checklist
+
+SonarCloud and Sourcery review every PR. This list collects what they have flagged on this repo's merged PRs (#28–#94), so a change can be checked against it before the PR opens. Numbers in brackets are example PRs.
+
+## SonarCloud rules this repo keeps hitting
</code_context>
<issue_to_address>
**Forks lack Sonar analysis**

When a pull request comes from a fork, `build.yml` skips SonarCloud analysis when `SONAR_TOKEN` is unavailable, while the checklist promises analysis for every PR and `AGENTS.md` tells authors to wait for its report. The local alternative also requires a Sonar token, leaving fork contributors without a way to satisfy the stated check.

Revise the guidance to state when Sonar analysis is unavailable and provide an actionable alternative for fork contributors.

Also at `AGENTS.md:29-34`.
</issue_to_address>

### Comment 6
<location path=".github/REVIEW_CHECKLIST.md" line_range="54" />
<code_context>
+- **Public API compatibility.** Changing a public constructor or record breaks callers. The project is unreleased (0.1.0-SNAPSHOT), so breaks are allowed, but say so in the PR body to save a review thread. [#39, #52, #63, #82, #83, #84, #87]
</code_context>
<issue_to_address>
**API breaks skip required migration guidance**

When a change intentionally breaks a public API, the checklist says unreleased status allows public API breaks and asks only for a PR-body note, so a break can ship without the migration path and version bump the constitution requires; callers then lack the documented transition.

Align the checklist with the constitution by requiring a migration path and a version bump for public API breaks, even while the project is unreleased.
</issue_to_address>

### Comment 7
<location path="AGENTS.md" line_range="32" />
<code_context>
+- Local Sonar analysis: `./gradlew build sonar --no-parallel -Dsonar.branch.name=<branch>` (`--no-parallel` as in CI). It uploads a short-lived branch analysis to SonarCloud with the PR gate's rules and needs a token in `systemProp.sonar.token` in `~/.gradle/gradle.properties` (never read or print that file). Then read the branch's open issues (`https://sonarcloud.io/api/issues/search?componentKeys=jircd_jircd-server&branch=<branch>&resolved=false`) and fix them before opening the PR. The SonarQube CLI's `sonar analyze` only scans secrets on this plan; its code analysis returns 403.
</code_context>
<issue_to_address>
**Sonar results can be stale**

When the issues endpoint is queried before SonarCloud finishes processing the uploaded report, `./gradlew sonar` can finish after uploading the report while SonarCloud is still processing it. Querying `/api/issues/search` before processing completes returns the branch's previous results, so a clean or stale response can be mistaken for the new analysis.

Wait for the analysis task to complete successfully and verify its commit before querying branch issues.
</issue_to_address>

Sourcery assessment

Approval pending. 7 findings to address first.

Blocking findings: AGENTS.md:32, AGENTS.md:32, .github/REVIEW_CHECKLIST.md:48, AGENTS.md:32, .github/REVIEW_CHECKLIST.md:3, and 2 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
Comment thread .github/REVIEW_CHECKLIST.md Outdated
Comment thread AGENTS.md Outdated
Comment thread .github/REVIEW_CHECKLIST.md Outdated
Comment thread .github/REVIEW_CHECKLIST.md Outdated
Comment thread AGENTS.md Outdated
Review follow-ups:

- Run the token-bearing scan only on trusted worktree branches, since
  the build executes the branch's Gradle logic with the token present.
- Take the branch name from `git branch --show-current`, quoted, instead
  of a hand-substituted placeholder.
- Wait for SonarCloud to process the analysis (sonar.qualitygate.wait)
  so the issue query doesn't read the previous one, and page the query.
- Say when SonarCloud doesn't run (no SONAR_TOKEN, fork PRs).
- A missing bind address binds every interface where the contract says
  so, as ServerConfiguration.Listener documents; the checklist had it
  backwards.
- Public API breaks follow the constitution's migration-path rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@codegeek
codegeek merged commit a2e62cd into main Oct 22, 2026
6 checks passed
@codegeek
codegeek deleted the docs/pre-pr-checks branch October 22, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant