Skip to content

feat: add --force-tag-creation option to explicitly create git tags for releases.#2627

Merged
chingor13 merged 6 commits intogoogleapis:mainfrom
Fanzzzd:feat/draft-release-force-tag
Jan 20, 2026
Merged

feat: add --force-tag-creation option to explicitly create git tags for releases.#2627
chingor13 merged 6 commits intogoogleapis:mainfrom
Fanzzzd:feat/draft-release-force-tag

Conversation

@Fanzzzd
Copy link
Copy Markdown
Contributor

@Fanzzzd Fanzzzd commented Nov 23, 2025

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:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #1650

@Fanzzzd Fanzzzd requested a review from a team as a code owner November 23, 2025 08:00
@Fanzzzd Fanzzzd requested a review from a team November 23, 2025 08:00
@product-auto-label product-auto-label bot added the size: l Pull request size is large. label Nov 23, 2025
@Fanzzzd
Copy link
Copy Markdown
Contributor Author

Fanzzzd commented Nov 23, 2025

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"
}

@Fanzzzd
Copy link
Copy Markdown
Contributor Author

Fanzzzd commented Nov 23, 2025

And I also forked the release-please action to test this branch more easily.

You can replace:

-  uses: googleapis/release-please-action@v4

in your release-please.yml with:

-  uses: Fanzzzd/release-please-action@release-with-force-tag

to try this new feature.

Here is the link for your interest: [Fanzzzd/release-please-action]

Copy link
Copy Markdown
Collaborator

@ferrarimarco ferrarimarco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR. A few things to check, then I'll have a look at testing this.

…iption across code, schema, and documentation.
@Fanzzzd
Copy link
Copy Markdown
Contributor Author

Fanzzzd commented Dec 20, 2025

@ferrarimarco Thank you for the thorough review! I've addressed all your feedback:

  1. Renamed --force-tag to --force-tag-creation to better convey what the option does.
  2. Updated the description to "Force the creation of a Git tag for the release."
  3. Extended the documentation to explain why this is useful when draft is enabled - GitHub does not create tags for draft releases until they are published, which causes release-please to fail to find the previous release.

Please let me know if there's anything else you'd like me to adjust.

Copy link
Copy Markdown
Collaborator

@ferrarimarco ferrarimarco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Added two minor comments.

@erka
Copy link
Copy Markdown

erka commented Jan 19, 2026

Hey @Fanzzzd and @ferrarimarco. Any progress with this? How could we help to push it forward?

Fanzzzd and others added 2 commits January 20, 2026 08:21
- add debug log on 422 when creating the tag
- revert unrelated formatting changes in config schema
@Fanzzzd
Copy link
Copy Markdown
Contributor Author

Fanzzzd commented Jan 20, 2026

@ferrarimarco , thank you for the review. I have addressed the two issues you mentioned.

@Fanzzzd Fanzzzd force-pushed the feat/draft-release-force-tag branch from 2c78dff to 5dfb877 Compare January 20, 2026 08:46
@chingor13 chingor13 merged commit e3eba37 into googleapis:main Jan 20, 2026
11 of 12 checks passed
@erka
Copy link
Copy Markdown

erka commented Jan 20, 2026

Thank you all so much!

sou06912-design

This comment was marked as spam.

WilliamBerryiii added a commit to microsoft/hve-core that referenced this pull request Feb 14, 2026
…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.
WilliamBerryiii added a commit to microsoft/hve-core that referenced this pull request Feb 19, 2026
…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
@Fanzzzd Fanzzzd changed the title feat: add --force-tag option to explicitly create git tags for releases. feat: add --force-tag-creation option to explicitly create git tags for releases. Mar 3, 2026
@bendasgfyug
Copy link
Copy Markdown

(compileroptioned":tsbuildinfofile":node modules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: l Pull request size is large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using draft: true causes release-pr to fail to find the previous release.

6 participants