Release-Please Monorepo Configuration
Monorepo-specific release-please strategy: component tagging, per-package
extra-files, linked versions, and the shared→component tag migration. For
the single-repo workflow/manifest/config shape, the conventional-commit
version-bump rules, and compliance auditing, use
configure-plugin:configure-release-please.
When to Use This Skill
| Use this skill when... | Use the alternative when... |
|---|---|
| Configuring component/include-component-in-tag for a multi-package repo | Setting up a single-repo release-please (workflow, manifest, config shape) — use configure-plugin:configure-release-please |
| Adding a new package to a monorepo's release-please config | Auditing an existing setup against documented conventions — use configure-plugin:configure-release-please |
| Fixing duplicate-tag, multiple-paths, or per-package no-bump failures | Actually merging release-please PRs — use release-please-pr-workflow |
| Migrating from shared v1.0.0 tags to component-v1.0.0 tags | Detecting manual edits to managed files — use release-please-protection |
| Setting per-package extra-files (JSON/YAML/TOML/XML version locations) | Release-As: trailer-based one-off overrides — use git-commit-trailers |
Core Files
| File | Purpose |
|------|---------|
| release-please-config.json | Per-package config, changelog sections, extra-files |
| .release-please-manifest.json | Current version for each package/component |
| .github/workflows/release-please.yml | GitHub Actions workflow (see configure-plugin:configure-release-please REFERENCE.md for the shape) |
Monorepo Configuration
Critical Settings for Monorepos
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"include-component-in-tag": true,
"separate-pull-requests": true,
"packages": {
"package-a": {
"component": "package-a",
"release-type": "simple",
"extra-files": ["package-a/version.json"]
}
}
}
Key Fields:
| Field | Required | Purpose |
|-------|----------|---------|
| include-component-in-tag | Yes (monorepo) | Creates package-a-v1.0.0 tags instead of v1.0.0 |
| component | Yes (monorepo) | Unique identifier for each package; must be set for every package |
| separate-pull-requests | Recommended | Creates per-package release PRs instead of combined |
Linked Versions
To keep a set of components on the same version, use the linked-versions
plugin:
{
"packages": {
"packages/frontend": {"release-type": "node", "component": "frontend"},
"packages/backend": {"release-type": "node", "component": "backend"}
},
"plugins": [
"node-workspace",
{
"type": "linked-versions",
"groupName": "workspace",
"components": ["frontend", "backend"]
}
]
}
Common Failure: Duplicate Release Tags
Symptom: Workflow fails with Duplicate release tag: v2.0.0
Cause: All packages try to create the same tag (e.g., v2.0.0) because:
- Missing
include-component-in-tag: trueat root level - Missing
componentfield in each package
Fix:
{
"include-component-in-tag": true,
"packages": {
"my-package": {
"component": "my-package", // Add this to every package
...
}
}
}
Common Failure: Multiple Paths Warning
Symptom: Multiple paths for : package-a, package-b
Cause: Empty component field (the : with nothing after it indicates empty string)
Fix: Ensure every package has "component": "package-name" set
Common Failure: Merged Release PR Never Tagged
Symptom: Every run after a grouped release PR merges aborts with:
There are untagged, merged release PRs outstanding - aborting
The workflow log also carries the tell:
pullRequestTitlePattern miss the part of '${component}'
pullRequestTitlePattern miss the part of '${scope}'
Cause: A custom pull-request-title-pattern (or group-pull-request-title-pattern)
containing ${version} — or ${component} / ${scope} — on a grouped release
PR (separate-pull-requests: false). Because a grouped PR spans many packages, there
is no single version/component to substitute, so the pattern renders a bare title
(e.g. chore: release with nothing after it). On the tagging pass release-please
re-parses the merged PR's title back through that same pattern to recover which
releases it represents; the bare title no longer matches the pattern, so the PR is
never tagged. It stays autorelease: pending, and every subsequent run refuses to
proceed while an untagged merged release PR is outstanding.
Fix: Drop the custom title patterns. The release-please defaults round-trip correctly for grouped PRs — they render a parseable title and the tagging pass recovers the releases from it.
// WRONG - ${version} on a grouped PR renders a bare, unparseable title
{
"separate-pull-requests": false,
"pull-request-title-pattern": "chore: release ${version}"
}
// CORRECT - omit the custom pattern; the default round-trips
{
"separate-pull-requests": false
}
Recovery: Unwedge an Already-Merged Untagged PR
Once a grouped release PR has merged with a bare title, dropping the config fixes
future runs but the already-merged PR is still stuck at autorelease: pending,
so runs keep aborting. Relabel it to autorelease: tagged so release-please stops
treating it as outstanding, then dispatch a fresh run to cut the releases:
# Create the label first if the repo lacks it — release-please only
# auto-creates `autorelease: tagged` when it successfully tags a PR.
gh label create "autorelease: tagged" --color 2A7A2A --description "release-please tagged"
# Relabel the stuck merged PR
gh pr edit $PR_NUMBER --add-label "autorelease: tagged" --remove-label "autorelease: pending"
# Dispatch a fresh run — with no tags cut, it recomputes the same releases
# into a new, parseable release PR (or tags directly on the next merge).
gh workflow run release-please.yml
No tags were cut for the wedged PR, so release-please recomputes the identical set of releases on the next run; the relabel only removes the "outstanding" blocker.
Per-Package extra-files for Custom Version Locations
For JSON files, you must use the object format with type, path, and jsonpath:
{
"packages": {
"my-plugin": {
"release-type": "simple",
"extra-files": [
{"type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "$.version"}
]
}
}
}
Key insight: For monorepo packages, extra-files paths are relative to the
package directory, NOT the repo root. Release-please automatically prepends the
package path.
Common Mistakes:
- Using a simple string path for JSON files:
// WRONG - won't update the version field
"extra-files": [".claude-plugin/plugin.json"]
// CORRECT - uses JSON updater with jsonpath
"extra-files": [
{"type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "$.version"}
]
- Prepending the package path yourself (it gets doubled):
// WRONG - path becomes my-plugin/my-plugin/.claude-plugin/...
"extra-files": [
{"type": "json", "path": "my-plugin/.claude-plugin/plugin.json", "jsonpath": "$.version"}
]
// CORRECT - path is relative to the package directory
"extra-files": [
{"type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "$.version"}
]
File Type Formats:
| File Type | Format |
|-----------|--------|
| JSON | {"type": "json", "path": "...", "jsonpath": "$.version"} |
| YAML | {"type": "yaml", "path": "...", "jsonpath": "$.version"} |
| TOML | {"type": "toml", "path": "...", "jsonpath": "$.version"} |
| XML | {"type": "xml", "path": "...", "xpath": "//version"} |
| Plain text | "path/to/version.txt" (string is fine) |
Adding a New Package to a Monorepo
- Update
release-please-config.json:
{
"packages": {
"new-package": {
"component": "new-package",
"release-type": "simple",
"extra-files": [
{"type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "$.version"}
],
"changelog-sections": [...]
}
}
}
- Update
.release-please-manifest.json:
{
"new-package": "1.0.0"
}
- Create the initial version file in the package if needed.
For the standard changelog-sections set and release-type table, see
configure-plugin:configure-release-please.
Migrating from Shared Tags to Component Tags
When transitioning from v1.0.0 style tags to component-v1.0.0:
- Add
"include-component-in-tag": trueto config - Add
"component": "package-name"to each package - Old tags (
v1.0.0) will be ignored - New releases will create component-specific tags
- Close any pending combined release PRs
Note: Release-please scans for component-specific tags. The first run after migration creates release PRs for all packages with changes since the manifest version.
Monorepo Troubleshooting
One Package's PR Not Created (others fine)
Check:
- Are there releasable commits scoped to that package path since its last component tag?
- Does the commit scope match the package path?
- Is the package's
componentset and unique?
Wrong Version in a Package's Extra File
Ensure the package's extra-files paths are relative to the package
directory, not the repo root (release-please prepends the package path):
// Correct (package path is "my-package")
"extra-files": [{"type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "$.version"}]
For single-repo troubleshooting (no PR created at all, version not bumping,
CI not running on the release PR), see configure-plugin:configure-release-please.
Quick Reference
# Check latest release-please-action version
curl -s https://api.github.com/repos/googleapis/release-please-action/releases/latest | jq -r '.tag_name'
# List pending release PRs (per-component in a monorepo)
gh pr list --label "autorelease: pending"
# View recent workflow runs
gh run list --workflow=release-please.yml --limit=5
# Inspect a package's current version in the manifest
jq -r '."my-package"' .release-please-manifest.json
Resources
- Manifest Releaser Guide — the canonical monorepo reference
- Release-Please Documentation
- Release-Please Action
configure-plugin:configure-release-please— single-repo standards, version-bump rules, compliance auditing