We use three layers of branches:
feature/fix branch -> staging -> main
(local work) (QA/verify) (production)
| Branch | Purpose | Direct pushes | Merges into it |
|---|---|---|---|
Work branches (feat/..., fix/..., chore/..., docs/...) |
Day-to-day development | Allowed by the author | — |
staging |
Integration and testing ground | Blocked | Work branches via PR |
main |
Production | Blocked | staging only, via PR |
main is production. Nothing is merged into main until it has run on staging.
-
Create a work branch off the latest
staging(notmain):git fetch origin git switch -c feat/my-change origin/staging
-
Make changes and run the checks locally:
npm ci npm ci --prefix frontend npm ci --prefix openclaw-service ./scripts/ci.sh all
-
Push the branch and open a pull request targeting
staging:git push -u origin feat/my-change gh pr create --base staging --fill
-
Get the required checks green, then merge into
staging. -
Verify the change on the staging deployment (or locally from
staging).
Once staging is verified and fit for release:
-
Open a promotion pull request from
stagingintomain:gh pr create --base main --head staging --title "Promote staging to production" --fill -
Two guards reject any PR into
mainwhose source branch is notstaging: thePromotion guardcheck (when GitHub Actions is available) andscripts/vercel-promotion-guard.mjsin the Vercel install step (always). -
Get the required checks green, then merge.
maindeploys to production.
staging is deployed to https://staging.stackstream.xyz and runs against
Stacks testnet, not mainnet. All other Vercel preview deployments (work
branches) also run against testnet; only production (main) runs against
mainnet.
The testnet configuration is stored as preview-scoped Vercel environment
variables, so it applies to staging and every other preview deployment:
NEXT_PUBLIC_NETWORK=testnetNEXT_PUBLIC_CONTRACT_DEPLOYER=ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V
NEXT_PUBLIC_CHAIN_ID and NEXT_PUBLIC_RPC_URL are not read by the frontend.
The testnet contracts are deployed with:
clarinet deployments generate --testnet --medium-cost
clarinet deployments apply --testnet -d --no-dashboardThe deployer mnemonic lives in the gitignored settings/Testnet.toml; never
commit it. Testnet STX comes from the
Hiro faucet.
Staging is behind Vercel Authentication and is intentionally SSO-only while the project is maintained by a single person. External testers can be added later through Deployment Protection Exceptions when the team grows.
Hotfixes follow the same path: branch off staging, open a PR into staging,
verify, then promote staging to main. If production is broken and an
expedited release is unavoidable, an administrator may temporarily relax branch
protection for a staging to main promotion, but the fix must still pass the
Vercel promotion guard and be merged back into staging afterwards.
The checks live in scripts/ci.sh and run in two places:
.github/workflows/test.ymlruns them as three jobs on every pull request and on pushes tostagingandmain:- Contract and unit tests (
npm test) - Frontend typecheck, lint, and production build
- OpenClaw service typecheck
- Contract and unit tests (
- Vercel runs
scripts/ci.sh allas part of every deployment throughfrontend/vercel.json, so the requiredVercelcheck covers the same three groups. - The Vercel install step (
scripts/vercel-install.sh) runsscripts/vercel-promotion-guard.mjsbefore installing dependencies. It fails the build when an open PR targetsmainfrom a branch other thanstaging, so the promotion rule is enforced even while GitHub Actions is unavailable.
While GitHub Actions is unavailable (for example, an account billing lock),
branch protection requires only the Vercel check. Once Actions is healthy,
restore the three job checks on both branches and the Promotion guard check on
main.
The staging and main branches require a pull request, the required status
checks, and an up-to-date branch before merging. Force pushes and branch
deletion are blocked.