googleapis / googleapis/release-please
node-workspace with merge: false: manifest updates go to the first candidate only, and plugin-created PRs are missing the autorelease label
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 588
- Avg merge
- 12h 16m
- Merged PRs (30d)
- 7
Description
## Environment
- release-please 17.3.0 (bundled in release-please-action v4.4.1), manifest mode
- Behavior confirmed unchanged by reading the source at v17.10.3
- Monorepo: pnpm workspace, one shared library plus two services that depend on it via `workspace:*`
- Config (trimmed):
```json
{
"separate-pull-requests": true,
"plugins": [{ "type": "node-workspace", "merge": false }],
"packages": {
"packages/shared": { "release-type": "node", "component": "shared" },
"services/a": { "release-type": "node", "component": "a" },
"services/b": { "release-type": "node", "component": "b" }
}
}
```
## Steps to reproduce
1. Land releasable commits that touch only `packages/shared` (the two services have no releasable commits of their own).
2. Let release-please run. It computes a release for shared, and the node-workspace plugin creates dependency-bump candidates for both services.
3. Inspect the three resulting pull requests.
## Bug 1: all dependency-bump manifest entries are attached to the first candidate only
`WorkspacePlugin.run` pushes the whole `updatedPathVersions` map onto `newCandidates[0]` unconditionally, after the `if (this.merge)` block:
https://github.com/googleapis/release-please/blob/v17.10.3/src/plugins/workspace.ts#L179-L187
With `merge: true` this is correct (there is only one merged candidate). With `merge: false` there are multiple candidates, and `newCandidates[0]` is simply the first package in the post-order graph walk (in practice the alphabetically first dependent). Result, observed in real runs:
- Service A's release PR contains the `.release-please-manifest.json` bump for service A **and** service B.
- Service B's release PR contains **no** manifest update at all (plugin-created candidates only get package.json, package-lock.json and CHANGELOG updates; the per-candidate manifest update from `buildPullRequests` is only attached to strategy candidates).
- The library's own PR is fine (strategy candidate).
Consequences depend on merge order and are all bad:
- Merge A's PR while B's PR is closed or abandoned: the manifest now claims a version of B that was never released (a phantom version). On the next run release-please looks up the tag for that phantom version, cannot find it, and generates a release PR with an unbounded commit range.
- Merge B's PR: the manifest is not updated, so release-please immediately re-proposes the same version for B in a new PR.
## Bug 2: plugin-created candidates get `labels: []`, so the PRs never receive `autorelease: pending`
`NodeWorkspace.newCandidate` builds the pull request with an empty labels array:
https://github.com/googleapis/release-please/blob/v17.10.3/src/plugins/node-workspace.ts#L342
Strategy-built candidates get the configured labels, but dependency-bump-only PRs are created without `autorelease: pending`. In a run that created three PRs (library plus two dependents), the action log contains exactly one labeling call, for the strategy candidate only:
```
✔ Successfully added labels autorelease: pending to issue:
```
Consequences:
- `buildReleases` / `createReleases` filter merged PRs by that label (see `findMergedReleasePullRequests`), so merging an unlabeled dependency-bump PR silently creates no tag and no GitHub release, and the same version is re-proposed on the next run.
- `findOpenReleasePullRequests` also filters by the label, so the open PR is invisible to the bot: it is never force-push refreshed after other merges and stays permanently conflicted.
## Expected behavior
With `merge: false`:
1. Each candidate PR should carry the manifest entry for its own path (and only that entry).
2. Plugin-created candidates should receive the same labels as strategy candidates, so tagging and PR maintenance work.
## Workaround
We removed the node-workspace plugin and replaced the cascade with a small workflow: when the library publishes a release, the workflow pushes one `fix(deps): bump to X.Y.Z` commit that touches a marker file inside each consuming service's directory. Path attribution then produces normal strategy-built per-service release PRs with correct labels and manifest lines.
Contributor guide
Research direction
Start in src/plugins/workspace.ts around WorkspacePlugin.run and src/plugins/node-workspace.ts around NodeWorkspace.newCandidate. Trace how updatedPathVersions and labels flow into plugin-created candidates, then inspect findMergedReleasePullRequests and findOpenReleasePullRequests for their label filtering. Done means merge:false candidates receive only their own manifest entry and plugin-created PRs receive the configured autorelease label.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- release, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100