Skip to content

ci(ci): require a changelog entry beside an engine change - #336

Merged
danielplohmann merged 1 commit into
danielplohmann:masterfrom
r0ny123:ci/require-changelog-entry
Sep 11, 2026
Merged

ci(ci): require a changelog entry beside an engine change#336
danielplohmann merged 1 commit into
danielplohmann:masterfrom
r0ny123:ci/require-changelog-entry

Conversation

@r0ny123

@r0ny123 r0ny123 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

The one from #323 that is not on the original list, and the one you said lands first.

What it enforces

A PR touching src/smda/** also has to touch CHANGELOG.md, or carry the no-changelog label. Everything else passes untouched.

Built to your specifics: pull_request rather than pull_request_target, since it needs the changed-file list and no secrets, so the read-only token a fork PR receives is enough and no PR content is checked out or executed. File-level only — a PR that edits an older entry rather than Unreleased passes, which is the acceptable false negative for a check whose job is to be loud rather than clever.

Two choices worth stating because they are not obvious from the diff:

The job runs unconditionally and decides inside the step, rather than skipping on an if. A skipped job reports as neither passing nor failing, which is exactly the ambiguity this check exists to remove — and it puts the reason a PR is exempt in the log, where the next reader looks. labeled and unlabeled are among the triggers, so applying the label re-runs the check instead of leaving a stale failure behind.

The label is matched with contains() against the label array rather than a joined string, so a label carrying a comma, or one this name is a prefix of, cannot be mistaken for it.

Verified

Driven locally against a stubbed file list, all eight outcomes:

changed files label result
src/smda/intel/x.py, tests/t.py fail
src/smda/intel/x.py, CHANGELOG.md pass
src/smda/intel/x.py no-changelog pass
tests/t.py, .github/workflows/ci.yml pass
(none) pass
src/smda/cil/y.py, docs/CHANGELOG.md fail
src/smda/aarch64/z.py (deletion) fail
src/smdax/q.py pass

zizmor --persona=regular reports no findings, which is what security.yml gates on. Full suite 2,110 passed, 1 skipped, 2,596 subtests; ruff clean; make typecheck exit 0 at master's own diagnostic count. No Python changes.

Two things to know before merging

It needs the no-changelog label to exist — you said you would create it. Referencing one that does not exist is harmless (the match simply never fires), so the order does not matter, but the escape hatch is not usable until it is there.

It changes what #327, #329 and #335 need. All three touch src/smda/** and none touches CHANGELOG.md, so once this and the format PR are both in, each needs an Unreleased entry on its next push. That is the intended demonstration rather than a problem — they are v4.7.0's content — and I will add the three entries once the format PR lands, unless you would rather write them.

It is also not a required check until you make it one in branch protection; until then it reports without blocking.

Closes nothing on its own — #323 stays open for the docs(docs) format PR, which I will raise against an empty Unreleased next.

The case for an Unreleased section is that each PR writes its own line
while the person who made the change still remembers the caveat. Nothing
enforced that, and at release time a missing entry looks exactly like a
change that did not need one -- only the author can tell the difference,
and only while the PR is open.

A PR touching src/smda/ now has to touch CHANGELOG.md, or carry the
no-changelog label for the cases that genuinely need no entry.

File-level only, deliberately: a PR that edits an older entry rather than
Unreleased passes. That is an acceptable false negative for a check whose
job is to be loud rather than clever.

pull_request rather than pull_request_target, since it needs the changed-
file list and no secrets. The job runs unconditionally and decides inside
the step rather than skipping on an if, so the check always reports a real
result and the reason a PR is exempt lands in the log. labeled/unlabeled
are among the triggers so applying the label re-runs the check instead of
leaving a stale failure behind.
@danielplohmann

Copy link
Copy Markdown
Owner

Reviewed against the workflow file rather than the table, and it is built to what #323 settled.

The two choices you called out are the right ones and worth keeping. A job that runs unconditionally and decides inside the step is the only shape that reports a real result — a skipped job is precisely the "neither passing nor failing" ambiguity the check exists to remove — and having labeled/unlabeled among the triggers is what makes the escape hatch usable without a rerun by hand. contains() against the label array rather than a joined string is the correct matcher; a prefix collision on a name this short is not hypothetical.

pull_request with contents: read + pull-requests: read, no checkout of PR content and no secrets, is enough for the files endpoint on the base repo, so fork PRs get a real verdict rather than a skip. grep -qx on CHANGELOG.md is what makes row 6 of your table fail, which is the behaviour I would want.

Two non-blocking notes, neither worth a change now:

  • A PR that deletes CHANGELOG.md satisfies the check, since the file is in the changed list either way. Not a shape worth defending against with a file-level check.
  • It will not report on the three PRs already open until each pushes again. They are all about to, so this costs nothing — but it is why the check reads green on a tree it has not actually judged.

Taking it as-is. The no-changelog label exists. Not making it required in branch protection yet: let it report on a few PRs first.

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.

2 participants