Enforce the pull request template with a required status check - #182
Conversation
The template was a prefill only. GitHub inserts it in the web UI and never
validates the result, and `gh pr create --body`/`--body-file` bypasses it
outright, so pull requests could be opened in an arbitrary shape.
Add a `pr-template` job that checks the body against
.github/PULL_REQUEST_TEMPLATE.md. The required headings are read out of the
template itself rather than duplicated in the checker, so editing the template
cannot leave the check behind.
The check validates shape, not substance: every section present, none empty, no
unfilled stub left behind. It does not require checklist boxes to be ticked,
which would only train contributors to tick them. The escape hatches the
template documents ("No Improvement", "No production Java code changed",
"Not reproducible from an exercise") are honoured.
The body is untrusted input on fork pull requests, so it is passed through the
environment and read with os.environ rather than interpolated into the shell,
which would be a script injection sink. The job carries no `paths` filter: a
required check that never runs would leave a pull request blocked instead of
passing.
Also document the rule in AGENTS.md, including the reason command-line pull
request creation silently skips the template.
Verified against all ten open pull request bodies (no false positives) and
against an empty body, a foreign template, and the template left unfilled
(all correctly rejected).
|
Warning Review limit reached
Next review available in: 22 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
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. Comment |
The checker was the repository's only Python file. Nothing linted, formatted or tested it, and it needed its own `.gitattributes` rule, because the repo default of `* text=auto eol=crlf` would otherwise have checked it out with CRLF line endings on the Linux runners and broken its shebang. Rewrite it as a single-file Java program run through the source-code launcher (`java .github/scripts/CheckPullRequestTemplate.java`, Java 11 and later). No build step, no artefact, and no language added to a Java repository. The `.gitattributes` rule is reverted, since `.java` needs no exception: the file is passed to `java` rather than executed directly, so CRLF is harmless, exactly as it already is for the 671 sources under `src/`. The conversion exposed a defect in the Python version. Its empty-table-row rule was dead code: the guard meant to skip the Markdown separator row, `^\s*\|[\s:-]*\|`, also matches a row whose cells are blank, so the rule could never fire. A body pasting the unfilled coverage table passed. The guard was redundant to begin with, since a separator row's cells are not whitespace, so it is dropped rather than repaired. Line endings are now normalised on input instead of being absorbed by whitespace trimming. The repository checks Markdown out as CRLF and GitHub delivers bodies with CRLF, so this removes a class of latent pattern bugs. Verified against all ten open pull request bodies (no false positives) and four rejection cases: an empty body, foreign headings, the template left unfilled, and the unfilled coverage table that the Python version wrongly accepted.
Summary
Adds a
pr-templatecheck that validates the pull request body against.github/PULL_REQUEST_TEMPLATE.md, and documents the rule inAGENTS.md. The checker isa single-file Java program. Intended to become a required status check once merged.
Linked issues
None.
1. Problem
The pull request template is a prefill, not a rule. GitHub inserts
.github/PULL_REQUEST_TEMPLATE.mdinto the description box in the web UI and then neverlooks at it again: no ruleset, branch protection or CODEOWNERS setting references it, and
nothing validates what is actually submitted.
Creating a pull request from the command line skips even the prefill.
gh pr create --bodyand--body-fileset the description verbatim, so a contributor or automatedagent working through the CLI never sees the template at all. The observed failure is a
pull request opened with headings from an entirely different project's conventions, which
a reviewer then cannot use: no linked issues, no testing manual, no statement of whether
the change is a false negative or a false positive, no breaking-change declaration.
The root cause sits in the CI configuration rather than in Ares itself: the repository
documented a contribution shape it had no mechanism to require. It is neither a false
negative nor a false positive in the security sense, because no student code is involved;
the cost is borne entirely by review quality.
2. Improvement from the user's perspective
No Improvement.
3. Improvement from the maintainer's perspective
A reviewer can rely on the body being complete before spending time on the diff. The
sections that most often go missing are exactly the ones a reviewer of a security tool
needs: the testing manual with its negative case, the modes exercised, and the
breaking-change declaration for an artefact consumed by exercise repositories.
The required headings are read out of the template at check time rather than duplicated
in the checker, so future edits to the template cannot silently leave the check behind.
AGENTS.mdgains the rule and, more usefully, the reason: agents that readAGENTS.mdnow learn that CLI pull request creation bypasses the template, which is the specific
mechanism behind the malformed bodies.
The checker is Java, run through the single-file source-code launcher, so the repository
stays monolingual. An earlier revision of this branch used Python; it was the only Python
file here, nothing linted or formatted it, and it needed its own
.gitattributesrule tosurvive the repository's
eol=crlfdefault with a working shebang..javaneeds no suchexception, because the file is passed to
javarather than executed directly.4. Testing manual
Prerequisites
actionlint. No Maven build or exercise is required.Steps
helper script, neither of which is reachable from an Ares exercise.
.github/workflows/actionlint.yml:actionlint -colorbodies that already follow the template:
one:
PR_BODY='' java .github/scripts/CheckPullRequestTemplate.javapr-templatejob on this pull request. The workflow runs here because apull_requestrun uses the merge ref, which already contains the new file.Expected result
PASSfor all ten open pull requests. This was the acceptance criterion:the job becomes a required check, so a single false positive would block a merge.
::error::The pull request body is empty. Start from .github/PULL_REQUEST_TEMPLATE.md and fill in every section.pr-templategreen, with a job summary readingAll 10 required sections are present and filled in.Negative case (what must still be rejected)
Three bodies must fail, and were confirmed to fail:
### Motivation/### Description. Rejected with oneMissing section headingerror per required section..github/PULL_REQUEST_TEMPLATE.mdpasted in unchanged. Rejected because every sectionis empty once the HTML comments are stripped. This case matters: pasting the template
without filling it in must not satisfy the check.
row. This is the case the earlier Python revision wrongly accepted, see below.
The check must remain permissive about substance. It deliberately does not require
checklist boxes to be ticked, since a check that demands ticks only trains contributors to
tick them without reading.
Modes exercised
No mode-specific behaviour changed.
5. Test case coverage regarding this PR
No production Java code changed.
Breaking changes and migration
None for consumers of the released artefact: the public API, the policy file format, the
generated security test code and the minimum JDK, Maven and Gradle versions are all
untouched.
For contributors, once the
pr-templatecheck is made a required status check, a pullrequest whose body does not follow the template will not be mergeable. All ten currently
open pull requests already pass, so nothing in flight is affected.
One defect worth calling out for reviewers, since it is the reason the second commit is
not a pure translation. The Python revision's empty-table-row rule was dead code. Its
guard against the Markdown separator row,
^\s*\|[\s:-]*\|, also matches a row whosecells are blank, so the rule could never fire, and a body pasting the unfilled coverage
table passed. The guard was redundant from the start, because a separator row's cells are
not whitespace, so the Java version drops it rather than repairing it. The Java version
also normalises line endings on input rather than relying on whitespace trimming to
absorb the carriage returns that both the CRLF checkout and the GitHub API deliver.
.gitattributesis unchanged frommainin the final state of this branch.Checklist
Review progress