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

Open
#2,853 0 comments 1 reaction 0 assignees View on GitHub
priority: p3
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.