feat(ci): split staging vs release workflows; release from staging tag#1096
feat(ci): split staging vs release workflows; release from staging tag#1096senamakel wants to merge 1 commit into
Conversation
tinyhumansai#1089) Splits the unified `release.yml` (which routed both production and staging through one `build_target` switch) into two first-class workflows: - `release-staging.yml` now creates immutable `staging/vX.Y.Z-N` tags on every successful cut. The base `X.Y.Z` is read from `app/package.json` without mutation; `N` is a monotonic counter computed from existing staging tags. Tag-only metadata keeps cadence noise-free (no version- bump commits land on `main` from staging). A cleanup job drops the staging tag if the build matrix fails so production never resolves to a half-built artifact. - `release.yml` is now production-only. Replaces the `build_target` switch with `release_source` (`staging_tag` default | `main_head`) plus an optional `staging_tag` pin. Empty pin = latest `staging/v*` tag by `creatordate`. The bump commit and release tag record source provenance in the message. - Concurrency groups are split (`release-staging` vs `release-production-main`) so neither workflow blocks the other. - New helpers: `scripts/release/next-staging-tag.js` (compute next staging tag) and `scripts/release/resolve-release-source.sh` (resolve release source ref + sha for traceability). - Runbook: `docs/RELEASE.md` documents both flows, tag conventions, rollback rules, and helper scripts. `CLAUDE.md` points at it. Closes tinyhumansai#1089
📝 WalkthroughWalkthroughThe PR splits a unified release workflow into separate staging and production workflows, adds immutable staging Git tags on each cut, and introduces helper scripts to compute next staging tags and resolve release sources. Both workflows are documented with updated guidance in the codebase. ChangesRelease Workflow Refactoring
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 3/5 reviews remaining, refill in 16 minutes and 38 seconds. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)
162-181:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
build_refstill points at the new release tag, not the resolved source.Lines 115-121 say the matrix should build the selected source, but Line 169 assigns
BUILD_REF="$TAG". That makes every downstream checkout build the freshly bumped commit onmain, so arelease_source=staging_tagrun can still ship code that staging never exercised.Either create the release bump/tag on top of the chosen source commit, or keep
build_refbound tosteps.source.outputs.ref/shaand inject the production version separately during packaging.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 162 - 181, The workflow sets BUILD_REF="$TAG" which causes downstream checkouts to build the new release tag rather than the selected source commit; change the logic so BUILD_REF is the resolved source (use steps.source.outputs.ref or steps.source.outputs.sha as the build ref) or, if you must keep TAG as the release identifier, create the bump/tag on top of that selected source commit before setting BUILD_REF; update the Resolve build outputs step to export build_ref from the source step (e.g., steps.source.outputs.ref or .sha) and keep TAG only for packaging/release metadata (symbols: BUILD_REF, TAG, steps.source.outputs.ref, steps.source.outputs.sha).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CLAUDE.md`:
- Around line 11-18: Summary: The table entry for **`app/`** still calls it a
"Yarn workspace" while the repo uses pnpm; update the wording to avoid
contradicting the note about pnpm. Fix: edit the CLAUDE.md table row for
**`app/`** to replace "Yarn workspace `openhuman-app`" with "pnpm workspace
`openhuman-app`" (or "pnpm workspace / Vite + React...") and ensure the
parenthetical sentence about repo migration remains consistent; check the
surrounding sentence that mentions "migrated from yarn to pnpm" and harmonize
capitalization/terminology if present.
In `@scripts/release/resolve-release-source.sh`:
- Around line 39-51: When RELEASE_SOURCE is set to a staging tag the script
currently accepts any tag value in STAGING_TAG; add a guard to reject tags that
are not actual staging tags by validating STAGING_TAG matches the expected
staging prefix (e.g. /^staging\// or 'staging/v*') before accepting it. Update
scripts/release/resolve-release-source.sh to check STAGING_TAG (the variable
used to set REF and compute SHA) and if it does not match the staging pattern,
print an error and exit nonzero; leave the existing git rev-parse checks for
refs/tags/${STAGING_TAG} in place after this validation.
---
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 162-181: The workflow sets BUILD_REF="$TAG" which causes
downstream checkouts to build the new release tag rather than the selected
source commit; change the logic so BUILD_REF is the resolved source (use
steps.source.outputs.ref or steps.source.outputs.sha as the build ref) or, if
you must keep TAG as the release identifier, create the bump/tag on top of that
selected source commit before setting BUILD_REF; update the Resolve build
outputs step to export build_ref from the source step (e.g.,
steps.source.outputs.ref or .sha) and keep TAG only for packaging/release
metadata (symbols: BUILD_REF, TAG, steps.source.outputs.ref,
steps.source.outputs.sha).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: f651ed18-afec-4c23-9d22-1f358741dbb3
📒 Files selected for processing (6)
.github/workflows/release-staging.yml.github/workflows/release.ymlCLAUDE.mddocs/RELEASE.mdscripts/release/next-staging-tag.jsscripts/release/resolve-release-source.sh
| | Path | Role | | ||
| | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | ||
| | **`app/`** | Yarn workspace `openhuman-app`: Vite + React (`app/src/`), Tauri desktop host (`app/src-tauri/`), Vitest tests | | ||
| | **`src/`** (root) | Rust lib `openhuman_core` + `openhuman` CLI binary — `core_server`, `openhuman::*` domains, MCP routing | | ||
| | **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman` produces the sidecar staged by `app`'s `core:stage` | | ||
| | **`docs/`** | Architecture and module guides | | ||
|
|
||
| Commands assume the **repo root**; `pnpm dev` delegates to the `app` workspace. (Repo migrated from yarn to pnpm — `package.json` enforces pnpm via the `packageManager` field.) |
There was a problem hiding this comment.
Replace the stale “Yarn workspace” label.
Line 13 still calls app/ a Yarn workspace even though Line 18 says the repo now enforces pnpm. That contradiction will send contributors to the wrong tooling terminology.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CLAUDE.md` around lines 11 - 18, Summary: The table entry for **`app/`**
still calls it a "Yarn workspace" while the repo uses pnpm; update the wording
to avoid contradicting the note about pnpm. Fix: edit the CLAUDE.md table row
for **`app/`** to replace "Yarn workspace `openhuman-app`" with "pnpm workspace
`openhuman-app`" (or "pnpm workspace / Vite + React...") and ensure the
parenthetical sentence about repo migration remains consistent; check the
surrounding sentence that mentions "migrated from yarn to pnpm" and harmonize
capitalization/terminology if present.
| if [ -z "$STAGING_TAG" ]; then | ||
| STAGING_TAG="$(git tag --list 'staging/v*' --sort=-creatordate | head -n 1)" | ||
| fi | ||
| if [ -z "$STAGING_TAG" ]; then | ||
| echo "[resolve-release-source] no staging tags found matching 'staging/v*' — push a staging cut first or rerun with release_source=main_head" >&2 | ||
| exit 1 | ||
| fi | ||
| if ! git rev-parse --verify --quiet "refs/tags/${STAGING_TAG}" >/dev/null; then | ||
| echo "[resolve-release-source] staging tag '${STAGING_TAG}' does not exist on this remote" >&2 | ||
| exit 1 | ||
| fi | ||
| REF="$STAGING_TAG" | ||
| SHA="$(git rev-parse "refs/tags/${STAGING_TAG}^{commit}")" |
There was a problem hiding this comment.
Reject non-staging tags when RELEASE_SOURCE=staging_tag.
This path currently accepts any existing tag because it only checks refs/tags/${STAGING_TAG}. A dispatch with staging_tag=v1.2.3 would pass here and build from a production tag, which defeats the staging-promotion contract.
Suggested guard
else
if [ -z "$STAGING_TAG" ]; then
STAGING_TAG="$(git tag --list 'staging/v*' --sort=-creatordate | head -n 1)"
fi
if [ -z "$STAGING_TAG" ]; then
echo "[resolve-release-source] no staging tags found matching 'staging/v*' — push a staging cut first or rerun with release_source=main_head" >&2
exit 1
fi
+ if ! printf '%s\n' "$STAGING_TAG" | grep -Eq '^staging/v[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$'; then
+ echo "[resolve-release-source] STAGING_TAG must match staging/vX.Y.Z-N, got '${STAGING_TAG}'" >&2
+ exit 1
+ fi
if ! git rev-parse --verify --quiet "refs/tags/${STAGING_TAG}" >/dev/null; then
echo "[resolve-release-source] staging tag '${STAGING_TAG}' does not exist on this remote" >&2
exit 1
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if [ -z "$STAGING_TAG" ]; then | |
| STAGING_TAG="$(git tag --list 'staging/v*' --sort=-creatordate | head -n 1)" | |
| fi | |
| if [ -z "$STAGING_TAG" ]; then | |
| echo "[resolve-release-source] no staging tags found matching 'staging/v*' — push a staging cut first or rerun with release_source=main_head" >&2 | |
| exit 1 | |
| fi | |
| if ! git rev-parse --verify --quiet "refs/tags/${STAGING_TAG}" >/dev/null; then | |
| echo "[resolve-release-source] staging tag '${STAGING_TAG}' does not exist on this remote" >&2 | |
| exit 1 | |
| fi | |
| REF="$STAGING_TAG" | |
| SHA="$(git rev-parse "refs/tags/${STAGING_TAG}^{commit}")" | |
| if [ -z "$STAGING_TAG" ]; then | |
| STAGING_TAG="$(git tag --list 'staging/v*' --sort=-creatordate | head -n 1)" | |
| fi | |
| if [ -z "$STAGING_TAG" ]; then | |
| echo "[resolve-release-source] no staging tags found matching 'staging/v*' — push a staging cut first or rerun with release_source=main_head" >&2 | |
| exit 1 | |
| fi | |
| if ! printf '%s\n' "$STAGING_TAG" | grep -Eq '^staging/v[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$'; then | |
| echo "[resolve-release-source] STAGING_TAG must match staging/vX.Y.Z-N, got '${STAGING_TAG}'" >&2 | |
| exit 1 | |
| fi | |
| if ! git rev-parse --verify --quiet "refs/tags/${STAGING_TAG}" >/dev/null; then | |
| echo "[resolve-release-source] staging tag '${STAGING_TAG}' does not exist on this remote" >&2 | |
| exit 1 | |
| fi | |
| REF="$STAGING_TAG" | |
| SHA="$(git rev-parse "refs/tags/${STAGING_TAG}^{commit}")" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/release/resolve-release-source.sh` around lines 39 - 51, When
RELEASE_SOURCE is set to a staging tag the script currently accepts any tag
value in STAGING_TAG; add a guard to reject tags that are not actual staging
tags by validating STAGING_TAG matches the expected staging prefix (e.g.
/^staging\// or 'staging/v*') before accepting it. Update
scripts/release/resolve-release-source.sh to check STAGING_TAG (the variable
used to set REF and compute SHA) and if it does not match the staging pattern,
print an error and exit nonzero; leave the existing git rev-parse checks for
refs/tags/${STAGING_TAG} in place after this validation.
Summary
Closes #1089. Splits the unified
release.yml(which routed both production and staging through onebuild_targetswitch) into two first-class workflows with separate concurrency, permissions, and artifacts.release-staging.yml): each successful cut now pushes an immutablestaging/vX.Y.Z-Ntag. BaseX.Y.Zreads fromapp/package.jsonwithout mutation;Nis a monotonic counter computed from existing staging tags (tag-only metadata, no version-bump commits land onmain). Acleanup-failed-stagingjob drops the tag if the matrix fails so production never resolves to a half-built artifact.release.yml): now production-only. Replacesbuild_targetwithrelease_source(staging_tagdefault |main_head) plus an optionalstaging_tagpin. Empty pin = lateststaging/v*bycreatordate. The bump commit and release tag recordsource/ref/shaprovenance. Production keepspatch/minor/major; default isminor.release-stagingandrelease-production-mainare now distinct groups so neither blocks the other.scripts/release/next-staging-tag.jsandscripts/release/resolve-release-source.sh.docs/RELEASE.mdrunbook covers both flows, tag conventions, rollback, and helper scripts.CLAUDE.mdpoints at it.Test plan
main; confirm astaging/v0.53.4-1tag is pushed and matrix succeeds-2and prior tag stays putrelease_source=staging_tag,staging_tag="",release_type=minor; confirm latest staging tag resolves, version bumps onmain,vX.(Y+1).0tag is pushed, and release shipsrelease_source=main_head,release_type=patch; confirm the hotfix path workscleanup-failed-stagingdrops the tagscripts/release/resolve-release-source.sherrors clearly when no staging tags existSummary by CodeRabbit
Documentation
Chores