Manifest mode: raw `<word>` in a BREAKING CHANGE footer is not HTML-escaped and silently skips component releases
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 73/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, typescript
- Domain
- release
Research direction
Start with src/changelog-notes/default.ts, where the issue identifies breaking-change note text being added without htmlEscape. Read the changelog note generation and the release PR body parsing path, then reproduce with the provided DefaultChangelogNotes and PullRequestBody example. Done means raw tag-like text in a BREAKING CHANGE footer no longer causes component release sections to be dropped.
Written by the indexing model from the issue text.
Description
Related to #2801, #2899 and #2884 (and the proposed fix in #2907), but a different source of the raw tag: the text comes from a BREAKING CHANGE: footer, which htmlEscape never sees.
Environment details
- OS: macOS 27 (also on
ubuntu-latestin GitHub Actions) - Node.js version: v26.13.0
- npm version: 11
release-pleaseversion: 17.11.2 (viagoogleapis/[email protected])- Manifest mode,
node-workspaceplugin, 17 packages, one grouped release PR
What happens
A commit pushed directly to the default branch had this footer:
BREAKING CHANGE: requires Biome 2.6.0 or newer. Extend presets as @kcconfigs/biome/configs/<name> instead of @kcconfigs/biome or @kcconfigs/biome/<name>.
Through the workspace plugin, that breaking-change note ended up in 11 of the 15 <details> sections of the release PR, with <name> unescaped. After the merge, release-please created 5 of 15 releases and logged Pull request contains releases, but not for component: ... for the rest. The workflow still succeeded. The manifest, package.json versions and CHANGELOGs all moved forward without tags, so the next release PR recomputed versions from older tags and proposed versions lower than ones already on npm.
The same happened on the release PR before it (2 of 15 tagged).
Real cases: https://github.com/kc-workspace/kcws/pull/285 and https://github.com/kc-workspace/kcws/pull/286
Root cause
In src/changelog-notes/default.ts, only the subject is escaped:
const notes = commit.notes
.filter(note => note.title === 'BREAKING CHANGE')
.map(note => replaceIssueLink(note, ...)); // no htmlEscape
let subject = htmlEscape(commit.bareMessage);
The breaking-change note text goes into the PR body raw. When the merged PR is parsed, node-html-parser opens <name> as an element, and every later <details> gets nested inside it and dropped (same mechanism as #2801).
Steps to reproduce
// npm i --ignore-scripts --save-exact [email protected]
const {DefaultChangelogNotes} = require('release-please/build/src/changelog-notes/default.js');
const {parseConventionalCommits} = require('release-please/build/src/commit.js');
const {PullRequestBody} = require('release-please/build/src/util/pull-request-body.js');
const {Version} = require('release-please/build/src/version.js');
(async () => {
const commits = parseConventionalCommits([{
sha: 'abc123', files: [],
message: 'feat!: move presets\n\nBREAKING CHANGE: extend presets as pkg/configs/<name> instead.',
}]);
const notes = await new DefaultChangelogNotes().buildNotes(commits, {
host: 'https://github.com', owner: 'o', repository: 'r',
version: '2.0.0', previousTag: 'v1.0.0', currentTag: 'v2.0.0', targetBranch: 'main',
});
console.log(notes.split('\n').filter(l => l.includes('name')).join('\n'));
// => * extend presets as pkg/configs/<name> instead.
const releaseData = ['pkg-a', 'pkg-b', 'pkg-c'].map(component => ({component, version: Version.parse('2.0.0'), notes}));
const body = new PullRequestBody(releaseData).toString();
console.log(PullRequestBody.parse(body).releaseData.map(d => d.component));
// => [ 'pkg-a' ] (pkg-b and pkg-c silently dropped)
})();
Workaround
Escaping the PR body by hand (<name>) is not enough, because of #2899: the first parse decodes it back to <name> before the strategies parse it again. What worked was editing the release PR body right before merging to replace <name> with something that isn't tag-like (for example {name}).
Suggested fix
Run the breaking-change note text through htmlEscape too, or apply the section-marker parsing from #2907 so note content can't affect section discovery at all.
- Dominant language
- TypeScript
- Stars
- 7.8k
- Forks
- 621
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 8
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from googleapis/release-please
-
BREAKING CHANGE footer with a continuation line keeps only the last linePossibly taken @kojiromike claimed this 7 days ago. Openpriority: p3 type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
googleapis/release-please#2955 · 1 comment ·
Maintainers usually reply within 2 days
-
Overflow release-notes.md is saved with latin1 encoding, corrupting non-ASCII characters (e.g. ⚠)Possibly taken @jay23606 claimed this 7 days ago. Openpriority: p3 type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
googleapis/release-please#2953 ·
Maintainers usually reply within 2 days
-
exclude-paths filters out empty Release-As commits in manifest modePossibly taken @anishesg claimed this 63 days ago. Openpriority: p3 type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
googleapis/release-please#2891 · 1 comment ·
Maintainers usually reply within 2 days
-
TypeError: Cannot read properties of undefined (reading 'match') when a commit subject contains a raw `<details>` token - crashes re-parsing of the open release pull request bodyPossibly taken @johnitvn claimed this 83 days ago. Openpriority: p3 type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
googleapis/release-please#2884 · 1 reaction ·
Maintainers usually reply within 2 days
-
cargo-workspace: cascaded dependent bumps ignore the active prerelease windowMay be free again A pull request for this issue was closed without being merged. Openpriority: p3 type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
googleapis/release-please#2828 · 2 comments ·
Maintainers usually reply within 2 days
All issues in googleapis/release-please
Similar issues
-
parse: child keys like `007`, `1e3`, `0x1f`, `-1` make the parent an empty array and hide the valueOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement follow-up
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 62/100
bketelsen/pi-pocket-team#232 ·
Maintainers usually reply within 1 day
-
bug UI
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
wardian-app/Wardian#1746 ·
Maintainers usually reply within 1 day
-
area: interop area: sip-core v3
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
fonoster/routr#705 · 1 comment ·
Maintainers usually reply within 1 day