feat: add --force-tag-creation option to explicitly create git tags for releases.#2627
Conversation
|
Use this PR to add "force-tag" to "release-please-config.json" to force the creation of tags. With this option, the draft releases can be traced by the force-created tags. This option is set to false by default, so it won't affect other implementations. Here is an example of how to use this option: {
"packages": {
"apps/desktop": {
"release-type": "node",
"package-name": "@acme/desktop",
"draft": true,
"force-tag": true, <---Set "force-tag" to true
"extra-files": [
"src-tauri/Cargo.toml",
"src-tauri/Cargo.lock"
]
},
"apps/web": {
"release-type": "node",
"package-name": "@acme/web",
"draft": true,
"force-tag": true, <---Set "force-tag" to true
}
},
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
} |
|
And I also forked the release-please action to test this branch more easily. You can replace: in your to try this new feature. Here is the link for your interest: [Fanzzzd/release-please-action] |
ferrarimarco
left a comment
There was a problem hiding this comment.
Thanks for this PR. A few things to check, then I'll have a look at testing this.
…iption across code, schema, and documentation.
|
@ferrarimarco Thank you for the thorough review! I've addressed all your feedback:
Please let me know if there's anything else you'd like me to adjust. |
ferrarimarco
left a comment
There was a problem hiding this comment.
Thanks. Added two minor comments.
|
Hey @Fanzzzd and @ferrarimarco. Any progress with this? How could we help to push it forward? |
- add debug log on 422 when creating the tag - revert unrelated formatting changes in config schema
|
@ferrarimarco , thank you for the review. I have addressed the two issues you mentioned. |
2c78dff to
5dfb877
Compare
|
Thank you all so much! |
…rors (#554) # Pull Request ## Description Fixes the persistent `HTTP 422: tag_name was used by an immutable release` error in the `publish-release` job. This is the **fifth iteration** (PRs #538 → #545 → #550 → #552) — all previous approaches failed because they attempted to delete and recreate releases or tags after publication, which GitHub's immutability model permanently forbids. ### Root Cause GitHub's [immutable releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#about-immutable-releases) states: > *"Git tags cannot be moved or deleted"* for immutable releases. *"Even if you delete a repository and create a new one with the same name, you cannot reuse tags that were associated with immutable releases."* The tag name is **permanently tainted** at GitHub's infrastructure level once a release is published with immutability enabled. Every delete/recreate approach was fundamentally doomed: | PR | Approach | Failure Mode | |----|----------|-------------| | #538 | `"draft": true` in config | Race condition — release-please can't find draft releases (lazy tag) | | #545 | `gh release edit --draft=true` | Can't edit published immutable release | | #550 | Delete published, recreate as draft | Worked for upload, but publish step failed | | #552 | Delete draft, recreate as published | HTTP 422 — tag name permanently reserved | ### Solution: Draft-First Flow Follow [GitHub's recommended workflow](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#creating-immutable-releases): 1. **release-please creates release as draft** (`"draft": true` in config) 2. **Explicit git tag creation** via API (workaround for release-please's lazy tag behavior) 3. **Upload assets to the mutable draft** (existing `attest-and-upload` and `upload-plugin-packages` jobs) 4. **Publish**: `gh release edit --draft=false` — release becomes immutable with all assets already attached ### Draft Race Condition Workaround release-please with `"draft": true` uses lazy tag creation — the git tag isn't materialized until publish. Without the tag, release-please's GraphQL query returns null for `tag` and `tagCommit`, causing it to skip the draft and propose a bogus version bump. This was fixed upstream in [googleapis/release-please#2627](googleapis/release-please#2627) (`force-tag-creation` option), but the fix is **not yet released** in release-please-action (latest: v4.4.0 → release-please 17.1.3). We work around this by explicitly creating the git tag via API after draft creation. When a new release-please-action ships with PR #2627, replace the manual tag creation step with `"force-tag-creation": true` in `release-please-config.json`. ## Related Issue(s) Supersedes PRs #538, #545, #550, #552 ## Type of Change Select all that apply: **Code & Documentation:** - [x] Bug fix (non-breaking change fixing an issue) **Infrastructure & Configuration:** - [x] GitHub Actions workflow ## Testing - Verified YAML and JSON syntax (no lint errors) - Logic validated against [GitHub REST API docs](https://docs.github.com/en/rest/releases/releases), [immutable releases concept](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#about-immutable-releases), and [release-please issue #1650](googleapis/release-please#1650) - The draft-first approach avoids all immutability enforcement because assets are uploaded to mutable drafts, and publish is a single `--draft=false` edit (not a delete/create) ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) ## Security Considerations - [x] This PR does not contain any sensitive or NDA information - [x] Any new dependencies have been reviewed for security issues ## Additional Notes - **v2.3.7 remediation**: The v2.3.7 tag is permanently tainted. A manual release may be needed if v2.3.7 assets need to be published. The next release-please cycle (v2.3.8+) will use the draft-first flow and should succeed cleanly. - **Future upgrade**: Once `release-please-action` includes `force-tag-creation` support, the manual tag creation step can be replaced with a single config line. This is documented in a code comment referencing googleapis/release-please#2627.
…ajor-version PRs (#560) ## Description Prevents release-please from creating bogus v3.0.0 PRs when it runs on release commits. The root cause is a timing issue with `"draft": true` in the release-please config: draft releases use lazy tag creation, so the git tag is not materialized until the release is published. Without the tag, release-please cannot locate the draft on subsequent runs, version anchoring fails, and the manifest falls back to scanning all commits — picking up an old `BREAKING CHANGE` footer and proposing un-warranted major bumps. The fix adds `skip-github-pull-request` to the release-please action step, conditioned on `startsWith(github.event.head_commit.message, 'chore(main)')`. Release commits carry zero unreleased changes, so skipping PR creation loses nothing. The existing manual tag-creation step ensures subsequent runs find the tag regardless. - ci(workflows): added `skip-github-pull-request` conditional to `main.yml` release-please step, gated on release-commit prefix detection - ci(workflows): updated the workaround comment block to reference both the skip conditional and the manual tag creation as a paired mitigation - docs(instructions): added "YAML Expression Quoting" section to `workflows.instructions.md` documenting the colon-space plain scalar parsing issue and two acceptable solutions in preference order ### YAML Expression Quoting Investigation During implementation, `actionlint` rejected the bare expression `${{ startsWith(value, 'chore(main): release') }}` because the GitHub Actions single-quoted literal `': release'` contains a colon followed by whitespace. The YAML parser interprets that as a mapping value indicator inside a plain scalar, which is invalid. Seven alternatives were tested: | # | Expression | YAML Quoting | actionlint | Notes | |---|-----------|--------------|------------|-------| | 1 | `startsWith(msg, 'chore(main): release')` | bare | FAIL | `': release'` has `: ` | | 2 | `startsWith(msg, 'chore(main): release')` | `"${{ }}"` | PASS | Only double-quoted `with:` input in repo | | 3 | `startsWith(msg, 'chore(main)')` | bare | PASS | **Selected** — shorter prefix, no colon-space | | 4 | `format('{0}: release', 'chore(main)')` | bare | FAIL | `': release'` still has `: ` | | 5 | `format('{0}{1}{2}', 'chore(main)', ':', ' release')` | bare | PASS | Over-engineered | | 6 | `startsWith(msg, 'chore(main):')` | bare | PASS | Colon without space is fine | | 7 | `env.RELEASE_PREFIX` + `startsWith(msg, env.RELEASE_PREFIX)` | bare | PASS | Adds indirection | Option 3 was selected: the shorter prefix `'chore(main)'` avoids colon-space entirely, enabling bare `${{ }}` which matches every other `with:` input in the repo. The specificity tradeoff is acceptable since all `chore(main)` commits are release-please-generated. ## Related Issue(s) Fixes #559 ## Type of Change Select all that apply: **Code & Documentation:** - [x] Bug fix (non-breaking change fixing an issue) - [ ] New feature (non-breaking change adding functionality) - [ ] Breaking change (fix or feature causing existing functionality to change) - [ ] Documentation update **Infrastructure & Configuration:** - [x] GitHub Actions workflow - [ ] Linting configuration (markdown, PowerShell, etc.) - [ ] Security configuration - [ ] DevContainer configuration - [ ] Dependency update **AI Artifacts:** - [ ] Reviewed contribution with `prompt-builder` agent and addressed all feedback - [x] Copilot instructions (`.github/instructions/*.instructions.md`) - [ ] Copilot prompt (`.github/prompts/*.prompt.md`) - [ ] Copilot agent (`.github/agents/*.agent.md`) - [ ] Copilot skill (`.github/skills/*/SKILL.md`) > **Note for AI Artifact Contributors**: > > - **Agents**: Research, indexing/referencing other project (using standard VS Code GitHub Copilot/MCP tools), planning, and general implementation agents likely already exist. Review `.github/agents/` before creating new ones. > - **Skills**: Must include both bash and PowerShell scripts. See [Skills](../docs/contributing/skills.md). > - **Model Versions**: Only contributions targeting the **latest Anthropic and OpenAI models** will be accepted. Older model versions (e.g., GPT-3.5, Claude 3) will be rejected. > - See [Agents Not Accepted](../docs/contributing/custom-agents.md#agents-not-accepted) and [Model Version Requirements](../docs/contributing/ai-artifacts-common.md#model-version-requirements). **Other:** - [ ] Script/automation (`.ps1`, `.sh`, `.py`) - [ ] Other (please describe): ## Sample Prompts (for AI Artifact Contributions) <!-- Not applicable — the instructions addition documents a YAML parsing edge case for workflow authors --> ## Testing - `actionlint` passed on `main.yml` with the final expression form - `npm run lint:all` passed (all 8 validation stages: markdown, spell-check, frontmatter, link validation, PowerShell analysis, YAML linting, copyright headers, action version pinning) - Seven YAML expression quoting alternatives were systematically tested with `actionlint` to identify the root cause and select the cleanest fix (see investigation table above) ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) - [ ] Tests added for new functionality (if applicable) ### AI Artifact Contributions - [ ] Used `/prompt-analyze` to review contribution - [ ] Addressed all feedback from `prompt-builder` review - [ ] Verified contribution follows common standards and type-specific requirements ### Required Automated Checks The following validation commands must pass before merging: - [x] Markdown linting: `npm run lint:md` - [x] Spell checking: `npm run spell-check` - [x] Frontmatter validation: `npm run lint:frontmatter` - [x] Link validation: `npm run lint:md-links` - [x] PowerShell analysis: `npm run lint:ps` ## Security Considerations - [x] This PR does not contain any sensitive or NDA information - [ ] Any new dependencies have been reviewed for security issues - [x] Security-related scripts follow the principle of least privilege ## Additional Notes **Upstream references:** - [googleapis/release-please#2627](googleapis/release-please#2627) — `force-tag-creation` feature request that would eliminate both workarounds - [googleapis/release-please-action#962](googleapis/release-please-action#962) — draft release tag timing discussion - [googleapis/release-please-action#906](googleapis/release-please-action#906) — related version anchoring issue **Follow-up:** Once release-please-action ships a version containing `force-tag-creation` support (release-please#2627), both the `skip-github-pull-request` conditional and the manual tag-creation step can be replaced with `"force-tag-creation": true` in `release-please-config.json`. 🛡️ - Generated by Copilot
|
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
Fixes #1650