Skip to content

Check that a pull request follows the template - #116

Open
akolson wants to merge 12 commits into
mainfrom
pull-request-template-check
Open

akolson wants to merge 12 commits into
mainfrom
pull-request-template-check

Conversation

@akolson

@akolson akolson commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Contributors open pull requests that leave required sections of the template empty, and
reviewers ask for the missing information by hand. A body can carry every heading and still
say nothing under them.

The change

  • A new pull-request-template automation fails the run when the body does not carry the
    sections its template marks as required. It resolves the template the way GitHub does, the
    repo's own copy first and this repo's second, and reads it from the base branch, so a pull
    request cannot relax the rules it is judged by.
  • A section is required when its heading carries a <!-- REQUIRED --> comment. A template
    marking none requires every level-two heading, so a repo is strict rather than silently
    unchecked.
  • Four states fail: the heading is missing, the section is empty once comments are stripped,
    it still holds …, or it still reads word for word as the template wrote it. Every failure is
    named, because the job log is the only channel. Bot authors are exempt.
  • Both line endings are read the same way. The web form returns a body with CRLF, and the
    contents API returns the template with LF.
  • edited and reopened join the on: union, so a corrected body clears the check. Consumers
    take the widened template on the next sync run.

References

Closes #102.

#115 marked this repo's own template, and the three repos that keep their own took the same
markers in learningequality/kolibri-design-system#1381, learningequality/morango#397 and
learningequality/test-actions#102. All four are merged.

An earlier version of this branch also copied the shared template into every consumer. That is
reverted in c494075. GitHub already applies this repo's template to any repo holding none of its
own, and the check falls back to this repo's template, so it needs no copy in the consumer.

Reviewer guidance

  1. npm test. Expect 122 passing, 19 of them new.
  2. Read scripts/pull-request-template.js. Check that the template is read from
    pull_request.base.ref and never from the head.
  3. After merging, open a pull request in test-actions leaving a required section empty. Expect
    the check to fail and the log to name that section. Then fill the section in the description.
    Expect the next run to pass. Those are cases 1 to 4 of the issue's testing section, and they
    can only run after this merges, because the leaf is called at @main.

AI usage

I used Claude Code to write the check and its tests, to confirm that the web form returns CRLF
bodies while the contents API returns LF, and to run the suite and prek. I decided the marker
convention, which sections are required, and that the check reads the base branch. I reviewed
the diff.

Contributors open pull requests that leave required sections empty, and
reviewers ask for the missing information by hand.

The check resolves the template the way GitHub does, the repo's own copy first
and the shared one second, then reads which sections that template marks as
required. A section is required when its heading carries a REQUIRED comment,
and a template that marks none requires every section, so a repo is strict
rather than silently unchecked. It reports a required section that is missing,
empty, or still holding the placeholder, and it names every one of them,
because the job log is the only channel it has.

The template is read from the base branch, so a pull request cannot relax the
rules it is judged by. Bot authors are exempt, as the kolibri-design-system
changelog check already does.
GitHub applies an org default template in the web form only. A pull request
opened with gh pr create, the API, or a coding agent reads the working copy
instead, and the org file is not in it, so a contributor in a kolibri checkout
has no template to follow.

The sync now carries a list of files rather than one. A consumer is in sync
when every file it takes matches, one pull request carries every file that
drifted, and the declined and toolchain-conflict guards hold only when every
drifted file agrees.

A consumer keeping a template of its own keeps it. That is checked in all six
places GitHub reads a template from, and a path that cannot be read counts as
a template of its own, so a failed read never overwrites a file the run could
not see.

morango keeps its own template at the path the sync writes to, so existence
alone cannot tell a copy of ours from a template of theirs. The shared template
opens with a marker comment naming this repo, and at that one path a file
without the marker is left alone.
@akolson
akolson requested review from a team, leq-bot and rtibblesbot October 21, 2026 14:27
@rtibblesbot

rtibblesbot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Waiting for changes

Outcome: reviewed 81286a9

Last updated: 2026-10-22 23:22 UTC

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116 has no blocking findings. The leaf workflow, registry and multi-file sync match #102. CI passes.

  • Suggestions: ellipsis false positive, bot exemption, read-error fallback, per-file verdicts, sync PR text and dry-run output, three test gaps, and the unreported test-actions runs (all inline).
  • Nitpick: several added comments restate their code (e.g. scripts/pull-request-template.js:29-31, scripts/sync-automation-template.js:269-271). Drop them.

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.
  • Decide whether the org's coding-agent account should be exempt from the template check.
  • Confirm the per-file sync verdict behaviour for consumers that declined or deleted the template.
  • Confirm the test-actions end-to-end cases ran, or are planned after merge.

Written by rtibblesbot, an LLM-based coding agent.

Comment thread .github/workflows/pull-request-template.yml
Comment thread scripts/pull-request-template.js Outdated
Comment thread scripts/pull-request-template.js
Comment thread scripts/pull-request-template.js
Comment thread scripts/pull-request-template.js Outdated
Comment thread scripts/sync-automation-template.js Outdated
Comment thread scripts/sync-automation-template.js Outdated
Comment thread scripts/sync-automation-template.js Outdated
Comment thread scripts/sync-automation-template.test.js Outdated
Comment thread scripts/sync-automation-template.test.js Outdated
An ellipsis anywhere in a section failed it, so a summary reading "Adds a
Loading… spinner" was rejected. Only a line that is the placeholder on its own
counts now.

A 403 or 5xx reading a repo's own template fell back to the shared one, which
would fail a body that follows the template its author was given. The check is
skipped instead, matching what the sync does with a path it cannot read.

One verdict for every pending file misjudged two cases: a consumer who declined
the template was offered it again whenever automation.yml drifted, and a
consumer who merged it and then deleted it became a permanent
toolchain-conflict. Each file carries its own verdict now, only drifted files
are proposed, and the most severe of the rest is reported.

A consumer whose only drifted file was the template got a pull request titled
"Refresh automation.yml from the shared template", describing a file it did not
contain. The title and body are built from the files in the pull request, and
the result carries them so the dry run says which ones it would write.

The path list lived in both scripts and could drift, so it moved to constants.

Three tests were weak or missing. The uppercase-path test passed with that path
removed. Nothing checked that the real template carries the sync marker, whose
loss would silently stop the sync refreshing copies it placed. Nothing covered
verdicts across several pending files.
@akolson

akolson commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

On the nitpick in the review body: dropped in 784d984. The comments that restated their code are gone, including the two you named. The two left carry facts from outside the file: the contents API being case sensitive, and the morango template sitting at the path the sync writes to.

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: 10 of 11 prior findings settled; 1 still open.

CI passing.

  • suggestion (open): BOT_USERNAMES still makes the org's coding agent skip the template check (scripts/pull-request-template.js:99). The thread has no reply. Fix it, or say why the agent is exempt.
Prior-finding status

ACKNOWLEDGED — .github/workflows/pull-request-template.yml:12 — two acceptance criteria have no test
RESOLVED — scripts/pull-request-template.js — any … in a section fails it
UNADDRESSED — scripts/pull-request-template.js:99 — coding agent skips the check via BOT_USERNAMES
RESOLVED — scripts/pull-request-template.js:86 — non-404 read falls back to the org template
RESOLVED — scripts/pull-request-template.js — duplicated template path list
RESOLVED — scripts/pull-request-template.test.js — uppercase-path test passes without the path
RESOLVED — scripts/sync-automation-template.js:365 — sync PR title and body describe only automation.yml
RESOLVED — scripts/sync-automation-template.js:303 — dry run omits pending files
RESOLVED — scripts/sync-automation-template.js — one verdict for all files
RESOLVED — scripts/sync-automation-template.test.js:425 — no test merges verdicts across files
RESOLVED — scripts/sync-automation-template.test.js:50 — no test that the real template carries the marker

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.
  • Decide whether the org's coding agent should be exempt from the template check.
  • Confirm the sync now writing the PR template to consumer repos is how the org wants it rolled out.

Written by rtibblesbot, an LLM-based coding agent.

@akolson
akolson requested a review from rtibblesbot October 21, 2026 15:41

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No new commits since my last review (784d984).


Written by rtibblesbot, an LLM-based coding agent.

The check knew one placeholder, the lone `…` line that the shared template
uses. morango fills its Summary with `*Short description*`, so a body that
kept that line passed. kolibri-design-system has the same hole in its
Changelog, where its template writes a bullet skeleton.

A required section now fails when its text matches the same section in the
template. That needs no list of placeholder strings per repo. A part filled
section still passes, because the bullet labels a contributor is meant to keep
would otherwise fail.
@akolson
akolson force-pushed the pull-request-template-check branch from 79c20f7 to b1b7bac Compare October 21, 2026 19:45
The instruction to keep the marker sits next to a paragraph about morango's own
file at the same path, so it now names this repo's template and links to it.
The template sync paragraph landed between the list of shared automations and
the note that the spreadsheet is not one of them, so the note read as if it
were about the synced files.

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: 10 prior findings resolved, 1 acknowledged; 1 new suggestion inline.

CI passing.

Prior-finding status

RESOLVED — .github/workflows/pull-request-template.yml:12 — two acceptance criteria have no test
RESOLVED — scripts/pull-request-template.js — any … in a filled section fails it
ACKNOWLEDGED — scripts/pull-request-template.js:101 — coding agent skips the template check
RESOLVED — scripts/pull-request-template.js:88 — non-404 read falls back to org template
RESOLVED — scripts/pull-request-template.js — template path list duplicated
RESOLVED — scripts/pull-request-template.test.js — test passes without the uppercase path
RESOLVED — scripts/sync-automation-template.js:365 — sync PR title and body describe only automation.yml
RESOLVED — scripts/sync-automation-template.js:303 — dry-run output omits pending files
RESOLVED — scripts/sync-automation-template.js — one verdict for all files
RESOLVED — scripts/sync-automation-template.test.js:425 — no test for merged verdicts
RESOLVED — scripts/sync-automation-template.test.js:50 — no test that the real template carries the marker

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.

Written by rtibblesbot, an LLM-based coding agent.

Comment thread scripts/pull-request-template.js

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: 11 of 12 prior findings resolved; 1 still open (see below).

CI passing. The delta is wording only and raises nothing new.

  • suggestion (still open): scripts/pull-request-template.js:61: an untouched multi-line section still passes when the body uses CRLF line endings. Line 21 splits the body on \n only. Each line keeps its \r, so it never equals the template text. Split on /\r?\n/ and add a CRLF test. That thread has no reply yet.
Prior-finding status

RESOLVED — .github/workflows/pull-request-template.yml:12 — two acceptance criteria have no test
RESOLVED — scripts/pull-request-template.js — … anywhere in a section fails it
ACKNOWLEDGED — scripts/pull-request-template.js:101 — coding agent skips the template check
RESOLVED — scripts/pull-request-template.js:88 — non-404 read falls back to the org template
RESOLVED — scripts/pull-request-template.js — path list duplicates the sync script
RESOLVED — scripts/pull-request-template.test.js — uppercase-path test passes without the path
RESOLVED — scripts/sync-automation-template.js:365 — sync PR title/body describe only automation.yml
RESOLVED — scripts/sync-automation-template.js:303 — dry-run output omits pending files
RESOLVED — scripts/sync-automation-template.js — one verdict for all files
RESOLVED — scripts/sync-automation-template.test.js:425 — no test for merged verdicts
RESOLVED — scripts/sync-automation-template.test.js:50 — real template marker not pinned
UNADDRESSED — scripts/pull-request-template.js:61 — untouched multi-line section passes with CRLF bodies

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.
  • Confirm that the shared PR template check, which runs in every consumer repo, should fail PRs whose required sections are empty or still hold the template text.
  • Confirm that the sync now proposing .github/PULL_REQUEST_TEMPLATE.md to consumer repos, with a verdict per file, is the rollout you want.

Written by rtibblesbot, an LLM-based coding agent.

The marker line said "Edit it there", which left the reader to carry the repo
name across a full stop. It now names the repo and the path.

The constant the sync matches on follows the new wording. It names the repo
too, so another repo's own template cannot be mistaken for a copy of ours.
The GitHub web form returns a pull request body with CRLF line endings, and
the contents API returns the template with LF. Splitting on LF alone left a
carriage return on every body line but the last, so a multi line section left
as the template wrote it compared as different and passed.

Single line sections were unaffected, because the trim removed the lone
carriage return.

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: all 12 prior findings resolved. No new findings. CI passing.

Prior-finding status

RESOLVED — .github/workflows/pull-request-template.yml:12 — Two acceptance criteria have no test
RESOLVED — scripts/pull-request-template.js — Any … in a filled section fails it
RESOLVED — scripts/pull-request-template.js:101 — Coding agent skips the template check
RESOLVED — scripts/pull-request-template.js:88 — Non-404 read falls back to the org template
RESOLVED — scripts/pull-request-template.js — Path list duplicates sync script
RESOLVED — scripts/pull-request-template.test.js — Test passes without the uppercase path
RESOLVED — scripts/sync-automation-template.js:365 — Sync PR title/body describe only automation.yml
RESOLVED — scripts/sync-automation-template.js:303 — Dry-run output omits pending files
RESOLVED — scripts/sync-automation-template.js — One verdict for all files
RESOLVED — scripts/sync-automation-template.test.js:425 — No test for merged verdicts
RESOLVED — scripts/sync-automation-template.test.js:50 — No test that the real template carries the marker
RESOLVED — scripts/pull-request-template.js:61 — Untouched multi-line section passes with CRLF body

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.
  • Confirm a required-section check that fails PRs across every org repo is the intended rollout, including the bot exemption for the coding agent.
  • Confirm the sync script's per-file verdicts, which decide when consumer repos get a template sync PR.

Written by rtibblesbot, an LLM-based coding agent.

The test that used it now passes an empty body, which is what makes the case,
so the fixture was left unreferenced and eslint flagged it.

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: 11 of 12 prior findings resolved. 1 acknowledged. No new findings. CI passing.

Prior-finding status

RESOLVED — .github/workflows/pull-request-template.yml:12 — untested acceptance criteria
RESOLVED — scripts/pull-request-template.js — any … fails a filled section
ACKNOWLEDGED — scripts/pull-request-template.js:101 — coding agent skips the template check
RESOLVED — scripts/pull-request-template.js:88 — non-404 read falls back to org template
RESOLVED — scripts/pull-request-template.js — template path list duplicated
RESOLVED — scripts/pull-request-template.test.js — uppercase path test passes without it
RESOLVED — scripts/sync-automation-template.js:365 — sync PR title/body describe only automation.yml
RESOLVED — scripts/sync-automation-template.js:303 — dry-run omits pending files
RESOLVED — scripts/sync-automation-template.js — one verdict for all files
RESOLVED — scripts/sync-automation-template.test.js:425 — no multi-file verdict tests
RESOLVED — scripts/sync-automation-template.test.js:50 — real template marker untested
RESOLVED — scripts/pull-request-template.js:61 — CRLF multi-line section passes

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml, .github/workflows/sync-automation-template.yml.
  • Confirm the org-wide template check should fail PRs in every consumer repo, given its required-section rules are a new gate others will hit.
  • Confirm the template sync PRs it opens in consumer repos are wanted alongside automation.yml.

Written by rtibblesbot, an LLM-based coding agent.

@rtibbles rtibbles left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's undo the unnecessary PULL_REQUEST_TEMPLATE propagation logic.

Comment thread scripts/sync-automation-template.js Outdated
/**
* Opens one pull request per consumer repo whose copy of automation.yml has
* drifted from automation-template.yml.
* Opens one pull request per consumer repo whose copy of a shared file has

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am a bit confused by the edits to this file. I assumed at first it was trying to do the automatic version of what you had done previously, of conforming custom PR templates to the header markers this script needs - but instead it seems to be copying the PR template from this repository into each repository that doesn't have a custom template?

This is unnecessary, as having a PR template in this repository acts as the PR template in every other repo on this org with out one. I think probably best to revert the PR template copying changes, as they're doing work that Github already handles natively.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hrm, I see the rationale is that gh pr create doesn't respect this... but I kind of want people to be paying more attention than that, if that makes sense?

Either they should be manually opening the PR via the web UI and hence getting the org template, or they should know enough (or internally, be using le-skills) to do this properly.

I think reverting this might help us to more directly notice sloppy PRs - and then the remediation message handles it after the fact instead of trying to pre-empt.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ohhh, okay. Reverted the changes, should be good i think?

GitHub already applies this repo's template to any repo in the org holding
none of its own, so copying the file into each consumer repeated work GitHub
does natively.

The check stays. It reads a repo's own template when there is one and falls
back to this repo's, so it needs no copy in the consumer.
@akolson akolson changed the title Check that a pull request follows the template, and carry the template to consumers Check that a pull request follows the template Oct 22, 2026
@akolson
akolson requested a review from rtibblesbot October 22, 2026 17:58
@akolson
akolson requested a review from rtibbles October 22, 2026 18:06

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #116: 11 of 12 prior findings resolved. 1 acknowledged. No new findings. CI passing.

Prior-finding status

RESOLVED — .github/workflows/pull-request-template.yml:12 — two acceptance criteria have no test
RESOLVED — scripts/pull-request-template.js — any … in a filled section fails it
ACKNOWLEDGED — scripts/pull-request-template.js:101 — coding agent skips the template check
RESOLVED — scripts/pull-request-template.js:88 — non-404 read falls back to the org template
RESOLVED — scripts/pull-request-template.js — template path list duplicated across scripts
RESOLVED — scripts/pull-request-template.test.js — uppercase-path test passes without the path
RESOLVED — scripts/sync-automation-template.js — sync PR title/body describe only automation.yml (code removed)
RESOLVED — scripts/sync-automation-template.js — dry-run output omits pending files (code removed)
RESOLVED — scripts/sync-automation-template.js — one verdict for all files (code removed)
RESOLVED — scripts/sync-automation-template.test.js — no test merging verdicts across files (code removed)
RESOLVED — scripts/sync-automation-template.test.js — no test that the real template carries the marker (marker removed)
RESOLVED — scripts/pull-request-template.js:61 — CRLF body passes an untouched multi-line section

Next step: Needs a full code review by a human engineer.

  • Workflow steps changed: .github/workflows/automation-caller.yml, .github/workflows/automation-group-pull-request-target.yml, .github/workflows/automation.yml, .github/workflows/pull-request-template.yml.
  • Confirm a check that fails contributor PRs on unfilled template sections is wanted across every org repo that calls this workflow.
  • Confirm dropping template propagation through the sync (acceptance criterion 1) is settled rather than deferred to a follow-up.

Written by rtibblesbot, an LLM-based coding agent.

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.

Declare required sections in each pull request template and check them

3 participants