diff --git a/scripts/release.mjs b/scripts/release.mjs
index ae9f661dd..b5d1bc9f6 100755
--- a/scripts/release.mjs
+++ b/scripts/release.mjs
@@ -173,14 +173,23 @@ if (headerIdx === -1) {
// went out carrying v4.0.0's notes that way, and nothing failed. A mismatch
// now stops the release instead of publishing another version's words.
const articleEnd = changelogHtml.indexOf('', headerIdx);
-const listStart = changelogHtml.indexOf('
', headerIdx);
-const listEnd = changelogHtml.indexOf('
', listStart);
-if (listStart === -1 || listEnd === -1) fail('Changelog entry markup is malformed.');
-if (articleEnd !== -1 && listStart > articleEnd) {
- fail(`The changelog entry for "${cfg.changelogLabel}${version}" has no of its own. `
- + 'Its bullets are in a list this script cannot read, and the next entry\'s notes would ship instead.');
+if (articleEnd === -1) fail('Changelog entry markup is malformed.');
+// EVERY list in the entry, not the first. A long release is grouped into
+// themed s behind cf-group labels, and taking only the first published one
+// theme and silently dropped the rest.
+const entryScope = changelogHtml.slice(headerIdx, articleEnd);
+const lists = entryScope.match(/[\s\S]*?<\/ul>/g);
+if (!lists || !lists.length) {
+ // An unclosed list and a missing one are different repairs, so they get
+ // different messages. Reporting "no list" for markup that plainly has one
+ // sends you looking for the wrong thing.
+ const opened = entryScope.includes('');
+ fail(opened
+ ? `The changelog entry for "${cfg.changelogLabel}${version}" opens a that is never closed inside its . Fix the markup.`
+ : `The changelog entry for "${cfg.changelogLabel}${version}" has no of its own. `
+ + 'Its bullets are in a list this script cannot read, and no notes would ship.');
}
-const entryHtml = changelogHtml.slice(listStart, listEnd + '
'.length);
+const entryHtml = lists.join('\n');
const notes = htmlToMarkdown(entryHtml);
ok('extracted');