UN-3408 [MISC] Add automated release workflow and version validation#1915
Conversation
Add create-release.yaml for automated release creation with version_bump input (patch/minor/major), confirm_major gate, and dry_run mode. Add validate-tag job to production-build.yaml that blocks Docker builds on malformed tags or unexpected version increments. Prevents recurrence of the v0.152.0 -> v1.53.0 typo incident. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
WalkthroughAdds a new manual "Create Release" GitHub Actions workflow that computes and publishes semantic-version releases with validation and dry-run support, and augments the production-build workflow with a Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Actions as "GitHub Actions\nRunner"
participant AppToken as "Create GitHub App\nToken (actions/create-github-app-token)"
participant GitHubAPI as "GitHub API / Releases"
participant GHCLI as "gh CLI"
User->>Actions: workflow_dispatch (version_bump, confirm_major, pre_release, dry_run)
Actions->>AppToken: request app token
AppToken-->>Actions: app token
Actions->>Actions: validate branch name (main or vX.Y.Z-hotfix)
Actions->>GitHubAPI: fetch base tag (latest or matching hotfix line)
GitHubAPI-->>Actions: base tag (or error)
Actions->>Actions: compute next semver (patch/minor/major), enforce confirm_major
Actions->>GitHubAPI: check if computed tag already exists
GitHubAPI-->>Actions: tag exists? (yes/no)
alt dry_run == true
Actions->>Actions: write Dry Run Summary
else
Actions->>GHCLI: gh release create --notes-start-tag <prev> --target <ref> [--prerelease] [--latest]
GHCLI-->>GitHubAPI: create release
GitHubAPI-->>GHCLI: release created
GHCLI-->>Actions: success
Actions->>Actions: write Release Created summary
end
sequenceDiagram
participant Trigger as "Release/Event\nor workflow_dispatch"
participant Actions as "GitHub Actions\nRunner"
participant GitHubAPI as "GitHub API / Releases"
participant Build as "build-and-push"
participant Summary as "build-summary"
Trigger->>Actions: start production-build workflow
Actions->>GitHubAPI: (validate-tag) extract tag from event/inputs, validate ^vX.Y.Z$
GitHubAPI-->>Actions: prior tags (if needed)
Actions->>Actions: enforce new tag > prior tag (or skip if none)
alt validate-tag success
Actions->>Build: proceed with build-and-push (needs validated tag)
Build-->>Actions: build/push results
Actions->>Summary: generate summary (only if validation succeeded)
else
Actions-->>Trigger: fail early (validation error) — downstream blocked
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| .github/workflows/create-release.yaml | New workflow for automated release creation; solid overall (App token, concurrency lock, collision guard, dry-run), but the collision check runs unconditionally even in dry-run mode, causing dry runs to fail instead of showing the summary when the computed tag already exists as a pre-release. |
| .github/workflows/production-build.yaml | Adds validate-tag job with format + same-line monotonicity checks that gate build-and-push; the same-line approach cleanly resolves the earlier major-bump conflict, but a cross-line regression (e.g., v0.100.0 after v0.163.x) would pass validation since no prior releases exist on the new line. |
Sequence Diagram
sequenceDiagram
actor Dev
participant CRW as create-release.yaml
participant GHA as GitHub API
participant PBW as production-build.yaml
Dev->>CRW: workflow_dispatch (version_bump, dry_run, pre_release)
CRW->>CRW: Validate branch (main / vX.Y.Z-hotfix)
CRW->>CRW: Validate major confirmation gate
CRW->>GHA: Fetch latest release tag
GHA-->>CRW: LATEST_TAG
CRW->>CRW: Compute NEW_TAG (patch/minor/major bump)
CRW->>CRW: Equality + sort-V-C monotonicity check
CRW->>GHA: Check for tag collision (gh release view NEW_TAG)
alt dry_run == true
CRW->>CRW: Output Dry Run Summary (no release created)
else
CRW->>GHA: gh release create NEW_TAG (App token)
GHA-->>PBW: release:created event
PBW->>PBW: validate-tag format check
PBW->>GHA: gh release list (same major.minor line)
GHA-->>PBW: PREV_TAG
PBW->>PBW: Same-line monotonicity check
alt validation passes
PBW->>PBW: build-and-push (matrix: 7 services)
PBW->>PBW: build-summary + Slack notification
else validation fails
PBW->>PBW: build-and-push blocked
end
end
Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/create-release.yaml
Line: 177-188
Comment:
**Collision check runs unconditionally in dry-run mode**
The `Check for tag collision` step has no `if: github.event.inputs.dry_run != 'true'` guard. If the computed tag already exists as a pre-release or draft, the workflow exits with "Delete it first or pick a different bump type" and the `Dry run summary` step never executes — defeating the purpose of the dry run. The PR description states that dry-run should "show computed version, no tag created," which implies the summary should always be reached.
```suggestion
- name: Check for tag collision
id: collision-check
if: github.event.inputs.dry_run != 'true'
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
NEW_TAG: ${{ steps.compute-version.outputs.new_tag }}
run: |
# Guard against collisions with existing pre-releases or drafts
# (which are hidden by --exclude-pre-releases in the earlier lookup).
if gh release view "$NEW_TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then
echo "::error::A release with tag '$NEW_TAG' already exists (possibly as a pre-release or draft). Delete it first or pick a different bump type."
exit 1
fi
```
Alternatively, include a warning line about the collision in the dry-run summary itself.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/workflows/production-build.yaml
Line: 64-72
Comment:
**Same-line check allows cross-line regressions to pass**
The `PREV_TAG` lookup filters by the same `NEW_MAJOR_MINOR` line, and when no prior release exists on that line the step exits 0 with "new minor/major bump." This means a manually created tag like `v0.100.0` would pass `validate-tag` when the current latest is `v0.163.5`, because there are no prior releases on `v0.100.x`. Until the recommended tag-protection ruleset is in place (noted as a follow-up outside this PR), `validate-tag` is the last line of defence against regressive manual releases.
A cross-line monotonicity check against the global latest would close the gap:
```bash
# After same-line check passes, also verify the new tag is >= the global latest
GLOBAL_LATEST=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name // empty')
if [[ -n "$GLOBAL_LATEST" && "$GLOBAL_LATEST" != "$NEW_TAG" ]]; then
GLOBAL_VER="${GLOBAL_LATEST#v}"
if printf '%s\n%s\n' "$NEW_VER" "$GLOBAL_VER" | sort -V -C; then
echo "::error::New version $NEW_TAG is less than or equal to global latest $GLOBAL_LATEST."
exit 1
fi
fi
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "UN-3408 [MISC] Address PR review: hotfix..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/create-release.yaml:
- Around line 67-72: The workflow's "Fetch latest release version" step (id:
get-latest) uses gh release list without pre-releases, which causes LATEST_TAG
to ignore pre-release tags and leads gh release create to fail when promoting a
pre-release; update the get-latest logic that sets LATEST_TAG (the gh release
list invocation) to either include pre-releases in the query or add a new
branch: detect an existing tag first (e.g., query all releases or tags via gh
release list --include-pre-releases or gh api to list tags), and if a matching
pre-release tag exists skip creating a duplicate or perform a promotion flow
instead of creating the same tag; ensure the subsequent gh release create call
checks for the tag's existence before attempting to create it.
In @.github/workflows/production-build.yaml:
- Around line 43-44: The "Validate version increment" step currently only runs
when github.event_name == 'release', which lets workflow_dispatch bypass the
check; update the step's conditional so the validation runs for both release and
manual triggers (e.g., change the if on the "Validate version increment" step to
run when github.event_name == 'release' OR github.event_name ==
'workflow_dispatch') so manual workflow_dispatch runs cannot skip the
monotonicity validation.
- Around line 66-69: The validate-tag job in production-build.yaml currently
rejects any major bump by comparing NEW_MAJOR and PREV_MAJOR unconditionally;
change it to skip the major-version rejection when the workflow was triggered by
a release.created event that includes the originating approval signal
(confirm_major=true) from create-release.yaml. Update the condition around the
NEW_MAJOR vs PREV_MAJOR check (in the validate-tag job) to first detect
github.event_name == 'release' and then inspect github.event.release.body (or a
chosen release field/label) for the confirm_major=true marker coming from
create-release.yaml; only enforce the fatal exit if no such approval marker is
present (i.e., default behavior unchanged for pushes and unapproved releases).
- Around line 50-51: The PREV_TAG lookup uses gh release list with
--exclude-pre-releases which hides recent pre-releases and makes .[1] point to
the wrong baseline; update the gh release list invocation used to compute
PREV_TAG so it does not exclude pre-releases (remove the --exclude-pre-releases
flag) and still excludes drafts, ensuring the returned list includes the latest
pre-release so .[1].tagName correctly references the previous release whether it
was a pre-release or stable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a53bf7a2-8603-4b99-b978-26589be0b373
📒 Files selected for processing (2)
.github/workflows/create-release.yaml.github/workflows/production-build.yaml
jaseemjaskp
left a comment
There was a problem hiding this comment.
PR Review Summary
Critical Issues (3)
1. jq returns literal "null" string, bypassing empty check
File: production-build.yaml — "Validate version increment" step
When only one release exists, .[1].tagName returns null, which jq -r converts to the string "null" — not an empty string. The -z "$PREV_TAG" check won't catch it, and subsequent cut operations will parse "null" as a version, producing a misleading error about major version changes.
Fix: Use // empty in the jq expression:
PREV_TAG=$(gh release list ... --jq '.[1].tagName // empty')2. sort -V -C allows equal versions (both files)
Files: create-release.yaml (~line 119), production-build.yaml (~line 62)
sort -V -C checks non-decreasing order, so equal versions pass. Combined with the missing default case in the case statement (see Suggestion 1 below), an unknown bump type could leave the version unchanged and the check would not catch it.
Fix: Add an explicit equality check:
if [[ "$CURRENT_NUM" == "$NEW_NUM" ]] || ! printf '%s\n%s\n' "$CURRENT_NUM" "$NEW_NUM" | sort -V -C; then
echo "::error::Version $NEW_TAG is not strictly greater than $LATEST_TAG"
exit 1
fi3. Race condition / ordering assumption with gh release list
Files: create-release.yaml (Fetch latest), production-build.yaml (Validate version increment)
gh release list returns releases ordered by creation date, not semver. If a backport or hotfix is created after a newer release, positional indexing (.[0], .[1]) returns the wrong version.
Fix for create-release.yaml — use GitHub's "latest" API:
LATEST_TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name')Fix for production-build.yaml — filter out the current tag:
PREV_TAG=$(gh release list --limit 10 --exclude-drafts --exclude-pre-releases --json tagName \
--jq "[.[] | select(.tagName != \"$NEW_TAG\")] | .[0].tagName // empty")Important Issues (5)
4. Summary step runs even when release creation fails
File: create-release.yaml — "Summary" step
The if: github.event.inputs.dry_run != 'true' condition replaces the default success() check. If gh release create fails, the summary still writes "Release Created" to GITHUB_STEP_SUMMARY.
Fix: if: github.event.inputs.dry_run != 'true' && success()
5. Major version block will reject legitimate major bumps from Create Release workflow
File: production-build.yaml — "Validate version increment" step
A user confirms the major bump via confirm_major in create-release.yaml, which creates a release — but then production-build.yaml fires on the release: created event and blocks the build because NEW_MAJOR != PREV_MAJOR. The two workflows contradict each other.
Fix: Either remove the major-version block from production-build.yaml (rely on the Create Release workflow's gate), or have the validate-tag step detect that the release was created by the release bot and skip the major check.
6. No branch restriction — releases can be cut from any branch
File: create-release.yaml
--target "${{ github.ref_name }}" uses whatever branch the workflow is dispatched from.
Fix: Add an early validation step:
- name: Validate branch
if: github.ref_name != 'main'
run: |
echo "::error::Releases must be created from the 'main' branch."
exit 17. Script injection surface on github.event.inputs.tag
File: production-build.yaml — existing "Set version tag" step
${{ github.event.inputs.tag }} is interpolated directly into a run: block. While validate-tag now catches bad formats, if the needs dependency is ever removed, this becomes exploitable.
Fix: Use an env: block instead of direct interpolation:
env:
TAG: ${{ github.event.release.tag_name || github.event.inputs.tag }}
run: echo "DOCKER_VERSION_TAG=$TAG" >> $GITHUB_ENV8. build-summary runs with misleading output when validate-tag fails
File: production-build.yaml
build-summary has if: always() and needs: build-and-push. When validate-tag fails, build-and-push is skipped, but build-summary still runs and may produce misleading output.
Fix: Add validate-tag to build-summary's needs and gate on its success:
build-summary:
needs: [validate-tag, build-and-push]
if: always() && needs.validate-tag.result == 'success'Suggestions (5)
-
Add default case in
casestatement (create-release.yaml— "Compute next version"): If triggered via API with an unexpected bump type, no increment occurs. Add*) echo "::error::Unknown bump type: $BUMP_TYPE"; exit 1 ;; -
Add
set -euo pipefail(both files): Use job-leveldefaultsto prevent silent pipeline failures:defaults: run: shell: bash -euo pipefail {0}
-
Validate
PREV_TAGformat (production-build.yaml): Add avX.Y.Zregex check onPREV_TAGbefore parsing withcut. -
Add comment explaining GitHub App token (
create-release.yaml): The defaultGITHUB_TOKENcan't trigger downstream workflows — this rationale should be captured inline, not just in the PR description. -
Update "Safety-net" comment (
production-build.yaml): The tag protection ruleset doesn't exist yet (it's a follow-up), so this job is currently the only protection, not "defense-in-depth". Suggest rewording to reflect the current state.
Strengths
- Core design is sound — automating release creation to eliminate manual typos addresses the real problem well
- Concurrency group with
cancel-in-progress: falseis the right choice for releases - Dry run support is well thought out
- Error messages include specific values and actionable guidance
- PR description is exceptionally thorough
…on, safety fixes - create-release.yaml: branch-aware mode (main vs vX.Y.Z-hotfix), uses gh api releases/latest for main (immune to hotfix creation-date ordering), same-line lookup for hotfixes, explicit --latest flag per Hotfix Guide - Pre-release/draft collision check before creating a release - Equal-version guard (sort -V -C alone treats equal as valid) - Default case in bump type switch - Summary step gated on success() - Removed unused checkout step - set -euo pipefail via job defaults - production-build.yaml: same-line increment check (handles hotfixes correctly without contradicting create-release major bumps) - jq null -> empty via '// empty' pattern - workflow_dispatch now also validates increment (was bypass) - build-summary gated on validate-tag success - Set version tag uses env block (avoids user-input interpolation in run) - PREV_TAG format regex check before parsing Addresses review feedback from jaseemjaskp, coderabbitai, greptile-apps on PR #1915. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review feedback addressed — commit a274f13Thanks for the reviews @jaseemjaskp, @coderabbitai, @greptile-apps. All concerns validated against the actual code (several confirmed via Critical fixes
Correctness / robustness
Suggestions (all applied)
Hotfix support (new capability)
This preserves the existing hotfix workflow without requiring the manual release page (which the planned tag protection ruleset would block). Verification
Ready for re-review. |
Test ResultsSummary
Runner Tests - Full Report
SDK1 Tests - Full Report
|
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainI'll review the updated workflow files to verify all the fixes described. 🏁 Script executed: Length of output: 26105
✅ All critical and correctness fixes verifiedEvery item in your resolution table is correctly implemented in the code:
✅ Action versions confirmed currentThe action versions in Minor residual observations (non-blocking)
The implementation is solid. LGTM — happy to approve when you're ready. [approve] |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/production-build.yaml (1)
140-167:⚠️ Potential issue | 🟠 MajorDon't publish
latestfor GitHub pre-releases.The
releaseevent triggers for both full releases and pre-releases. Currently, this block assignslatestfor everyreleaseevent, which means a GitHub pre-release will overwrite stablelatestimages even though the release is explicitly marked as prerelease.Gate
latestongithub.event.release.prerelease != truewhile keepingset_as_latestas the explicit manual override.Suggested fix
- name: Set image tags id: tags + env: + RELEASE_PRERELEASE: ${{ github.event.release.prerelease }} run: | # Check if service exists in the config echo "Checking if service ${{ matrix.service_name }} exists in docker-compose.build.yaml" if ! grep -q "^ ${{ matrix.service_name }}:" ./docker/docker-compose.build.yaml; then echo "Service ${{ matrix.service_name }} not found in docker-compose.build.yaml" && exit 1 @@ - if [ "${{ github.event_name }}" = "release" ] || [ "${{ github.event.inputs.set_as_latest }}" = "true" ]; then + if { [ "${{ github.event_name }}" = "release" ] && [ "${RELEASE_PRERELEASE}" != "true" ]; } || [ "${{ github.event.inputs.set_as_latest }}" = "true" ]; then echo "LATEST_IMAGE_TAG=unstract/${{ matrix.service_name }}:latest" >> $GITHUB_ENV else echo "LATEST_IMAGE_TAG=" >> $GITHUB_ENV fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/production-build.yaml around lines 140 - 167, The current "Set image tags" step unconditionally treats any release event as a stable release and sets LATEST_IMAGE_TAG; change the conditional so LATEST_IMAGE_TAG is set only when the event is a non-prerelease release OR when the manual input set_as_latest is true. Update the if in the run block that sets LATEST_IMAGE_TAG (inside the "Set image tags" step referencing matrix.service_name and env.DOCKER_VERSION_TAG) to require github.event_name == "release" AND github.event.release.prerelease != true, or to allow the override when github.event.inputs.set_as_latest == "true" (i.e., ((release && prerelease != true) || set_as_latest == true)).
♻️ Duplicate comments (1)
.github/workflows/production-build.yaml (1)
64-67:⚠️ Potential issue | 🟠 MajorInclude pre-releases when selecting the same-line baseline.
Line 65 still excludes pre-releases from the
PREV_TAGlookup. That means a prerelease-only line can fall into the"No prior release"branch and skip monotonicity entirely, so a regressing prerelease on that line would still pass this guard. Compare against the latest same-line release regardless of prerelease status, or branch ongithub.event.release.prereleasewhen choosing the baseline.Suggested fix
- PREV_TAG=$(gh release list --repo "${{ github.repository }}" \ - --limit 100 --exclude-drafts --exclude-pre-releases \ + PREV_TAG=$(gh release list --repo "${{ github.repository }}" \ + --limit 100 --exclude-drafts \ --json tagName \ --jq "[.[] | select(.tagName != \"$NEW_TAG\" and (.tagName | startswith(\"v${NEW_MAJOR_MINOR}.\")))] | .[0].tagName // empty")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/production-build.yaml around lines 64 - 67, The PREV_TAG lookup excludes prereleases (the --exclude-pre-releases flag in the gh release list call that sets PREV_TAG), which causes prerelease-only lines to be treated as "No prior release"; update the logic so PREV_TAG compares against the latest same-line tag regardless of prerelease status by removing the --exclude-pre-releases filter from the gh release list call that uses NEW_TAG and NEW_MAJOR_MINOR, or alternatively branch on github.event.release.prerelease and choose the gh release list invocation accordingly (include prereleases when github.event.release.prerelease is true, keep excluding them otherwise) so the same-line baseline always considers prereleases when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In @.github/workflows/production-build.yaml:
- Around line 140-167: The current "Set image tags" step unconditionally treats
any release event as a stable release and sets LATEST_IMAGE_TAG; change the
conditional so LATEST_IMAGE_TAG is set only when the event is a non-prerelease
release OR when the manual input set_as_latest is true. Update the if in the run
block that sets LATEST_IMAGE_TAG (inside the "Set image tags" step referencing
matrix.service_name and env.DOCKER_VERSION_TAG) to require github.event_name ==
"release" AND github.event.release.prerelease != true, or to allow the override
when github.event.inputs.set_as_latest == "true" (i.e., ((release && prerelease
!= true) || set_as_latest == true)).
---
Duplicate comments:
In @.github/workflows/production-build.yaml:
- Around line 64-67: The PREV_TAG lookup excludes prereleases (the
--exclude-pre-releases flag in the gh release list call that sets PREV_TAG),
which causes prerelease-only lines to be treated as "No prior release"; update
the logic so PREV_TAG compares against the latest same-line tag regardless of
prerelease status by removing the --exclude-pre-releases filter from the gh
release list call that uses NEW_TAG and NEW_MAJOR_MINOR, or alternatively branch
on github.event.release.prerelease and choose the gh release list invocation
accordingly (include prereleases when github.event.release.prerelease is true,
keep excluding them otherwise) so the same-line baseline always considers
prereleases when appropriate.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3adb00c9-e67a-4f6a-866f-0c02e0e53998
📒 Files selected for processing (2)
.github/workflows/create-release.yaml.github/workflows/production-build.yaml
✅ Files skipped from review due to trivial changes (1)
- .github/workflows/create-release.yaml



What
.github/workflows/create-release.yamlworkflow for automated release creationworkflow_dispatchinputs:version_bump(patch/minor/major),confirm_major(gate),pre_release,dry_runvalidate-tagjob to.github/workflows/production-build.yaml^v[0-9]+\.[0-9]+\.[0-9]+$) and version incrementbuild-and-pushblocked vianeeds: validate-tagif validation failsWhy
Between
v0.152.0andv0.158.0, a manual typo on the GitHub release page caused versions to jump fromv0.152.0→v1.53.0, progress throughv1.57.1, and revert tov0.158.0. Six accidentally-createdv1.xtags now exist on the repo.Since
unstract-cloudfetches the latest OSS release frequently for RC creation, a bad OSS release can be picked up by cloud RCs before anyone notices. This PR introduces an automated release path to eliminate typos, plus a validation safety net.Slack thread: https://zipstack.slack.com/archives/C07TXK5239Q/p1776234492314379
How
create-release.yamluses the GitHub App token pattern (same asunstract-cloud/create-rc-build.yaml) because the defaultGITHUB_TOKENcannot trigger downstream workflowssort -V -Cvalidate-tagruns as a separate job sobuild-and-pushgetsneeds: validate-tag— bad tags block the buildconfirm_majorgate in create-release, unexpected-major-change error in validate-tagCan this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
No. The validation job is strict but only fails on malformed or regressing tags — valid releases pass through unchanged. The existing
build-and-pushflow is untouched apart from theneeds: validate-tagdependency. Existingworkflow_dispatchmanual triggers with correctly-formatted tags continue to work.Database Migrations
None.
Env Config
The following must be configured on the OSS repo for
create-release.yamlto work:PUSH_TO_MAIN_APP_IDPUSH_TO_MAIN_APP_PRIVATE_KEYcontents: writepermission installed on the repoWithout these, only the
validate-tagchanges inproduction-build.yamltake effect.Relevant Docs
Related Issues or PRs
unstract-cloud/.github/workflows/create-rc-build.yamlandpromote-rc-build-to-stable.yamlDependencies Versions
None.
Notes on Testing
Create Releaseworkflow withpatch→ verify next version computed correctly (e.g.v0.163.2→v0.163.3), tag + release created, production build triggered.dry_run = true→ summary shows computed version, no tag created.version_bump = majorwithconfirm_major = false→ fails with clear error.v1.200.0via UI →validate-tagblocks the build.tag = \"0.164.0\"→ fails format check.Follow-up (manual config, outside this PR)
Set up tag protection ruleset at
Settings → Rules → Rulesets:v*This is the primary defense — it closes the race window for cloud-RC consumption by preventing bad tags at the source.
Checklist
🤖 Generated with Claude Code