Regression: `<...>` inside inline code spans in commit subjects still break PR-body parsing, silently skipping a component release (manifest mode)
Author: eungkyuCreated Jun 2, 2026Updated Sep 10, 2026
Labelstype: bugpriority: p3
This is a regression / incomplete fix of #1659 (fixed by #1661). HTML tags inside inline code spans in commit subjects are intentionally left unescaped by htmlEscape, but they still break the PR-body re-parsing step, causing a component's release to be silently skipped.
Environment
release-please17.6.0 (googleapis/release-please-action@v5)- Manifest mode, multi-package monorepo,
include-component-in-tag: true
Root cause
src/changelog-notes/default.tshtmlEscape()escapes bare</>, but via the regex/[^].*[^]|`[^`]*`|<|>/gwithmatch.length > 1 ? match : ...it leaves</>inside inline code spans untouched. So a subject likeadd `check --report <path>`keeps a raw<path>in the generated release-PR body.- On release creation,
src/util/pull-request-body.tsextractMultipleReleases()re-parses the PR body withnode-html-parser. It does not understand markdown backticks, so<path>is treated as an unclosed HTML tag and the following<details>block(s) get nested inside it and dropped fromroot.getElementsByTagName('details')— the exact mechanism described in #1659. src/strategies/base.tsthen logsPull request contains releases, but not for component: <X>and skips that component: no git tag, no GitHub release, no publish — while the workflow still reports success.- The next release-please run can no longer find that component's release boundary (tag missing) and re-walks its entire history, producing a bogus major bump in the next release PR.
Steps to reproduce
- In a manifest repo with ≥2 packages, land a commit whose subject contains an inline code span with an HTML-tag-looking token, e.g.
feat: add `--report <path>` flag(affecting multiple packages so it appears in multiple<details>blocks). - Merge the release PR.
- Observe that the component carrying the 2nd occurrence is not released (no tag/release/publish), though the run succeeds.
Minimal reproduction
// npm i [email protected]
const { PullRequestBody } = require('release-please/build/src/util/pull-request-body.js');
const body = [
':robot: release', '---', '',
'<details><summary>pkg-a: 1.0.0</summary>\n\n### Features\n\n* add `--report <path>` flag\n</details>',
'<details><summary>pkg-b: 2.0.0</summary>\n\n### Features\n\n* add `--report <path>` flag\n</details>',
'---', 'footer',
].join('\n');
console.log(PullRequestBody.parse(body).releaseData.map(d => d.component));
// => [ 'pkg-a' ] <-- pkg-b silently dropped
console.log(PullRequestBody.parse(body.replaceAll('<path>', '<path>')).releaseData.map(d => d.component));
// => [ 'pkg-a', 'pkg-b' ] <-- escaping the angle brackets fixes itReal-world case: an 11-package release PR dropped exactly the package whose <details> block contained the 2nd <path> occurrence.
Suggested fix
Since the PR body is later HTML-parsed (not rendered as markdown), </> should be escaped even when inside code spans when generating the body. Alternatively, make extractMultipleReleases extract <details> blocks robustly so an unclosed tag in note text cannot corrupt the DOM and drop sibling blocks.
Source: googleapis/release-please