| name | release |
| description | Release pipeline for CLI, mobile, web, and server. Guides through version bumping, building, testing, publishing, and deploying. Replaces the old interactive release-it flow with a Claude Code-native experience. Use when user types /release or asks to release, publish, deploy, or ship any component.
|
Release
You are the release operator for the Happy monorepo. When invoked, walk the user through releasing the component they choose.
Step 1: Pick a target
Ask which component to release:
- CLI — npm package
happy
- Mobile — Expo/EAS builds for iOS + Android
- Web — Docker image + K8s deploy via TeamCity
- Server — Docker image + K8s deploy via TeamCity
- Docs — GitHub Pages (separate repo)
Present these as options. Wait for the user to pick.
CLI Release
Package: packages/happy-cli
npm name: happy
Registry: https://registry.npmjs.org
Git tags: cli-{version}
Tag namespace note:
- CLI releases use
cli-X.Y.Z
- Native releases use
native-<runtime-version>
- OTA releases use
ota-<ota-version>
- Do not use a bare
vX.Y.Z tag for Happy releases because multiple release streams coexist in this repo
Step 2: Gather state
Run these in parallel:
npm view happy dist-tags — see current latest + beta
cat packages/happy-cli/package.json | grep version — local version
git status --short — check for dirty state
git branch --show-current — confirm branch
git log --oneline -10 — recent commits for release notes context
Present a summary:
Local version: X.Y.Z
npm latest: X.Y.Z
npm beta: X.Y.Z-N
Branch: main
Working tree: clean / dirty
Step 3: Pick channel and version
Ask the user:
- Channel:
latest or beta
- Bump type: For latest:
patch, minor, major. For beta: prerelease (appends -N), or explicit version.
Suggest a sensible default based on the current state. For beta, the next prerelease of the current version. For latest, a patch bump.
Present as options. Wait for confirmation.
Step 4: Version bump
Edit packages/happy-cli/package.json directly — do NOT use npm version (it chokes on pnpm workspace protocol).
IMPORTANT: do this before build/test for the CLI. The build imports package.json and bakes the version into the generated bundle. If you build first and bump later, happy --version can still report the old prerelease version even though npm metadata shows the new one.
Step 5: Build
cd packages/happy-cli
pnpm --filter happy run build
Report success/failure. Stop on failure.
Step 6: Test (unit only)
cd packages/happy-cli
pnpm --filter happy exec vitest run --project unit
Integration tests are slow and flaky — skip them for releases. Unit tests are the gate.
Expect the unit suite to take around a minute; src/utils/serverConnectionErrors.test.ts is particularly slow, so don't mistake a long run for a hang.
Report results. If failures, ask the user whether to proceed or abort.
Step 7: Publish
cd packages/happy-cli
pnpm publish --tag {channel} --no-git-checks --ignore-scripts
--no-git-checks: allows dirty working tree (we already verified state)
--ignore-scripts: skips prepublishOnly (we already built and tested)
Step 8: Verify
npm view happy dist-tags
Confirm the new version appears under the correct tag.
If latest doesn't move immediately, wait 10-15 seconds and check again; npm tag propagation is not always instant.
Step 9: Git tag + commit (latest only)
For latest releases only:
- Commit the version bump:
Release version X.Y.Z
- Tag:
git tag cli-X.Y.Z
- Push:
git push && git push --tags
For beta releases: ask the user if they want to commit the version bump or leave it uncommitted.
If git push is rejected because origin/main advanced while releasing, fetch and rebase the release commit before retrying:
git fetch origin main
git rebase --autostash origin/main
git tag -f cli-X.Y.Z
git push && git push --tags
Use --autostash when the worktree is dirty from unrelated local changes so those edits are preserved. Recreate the tag after rebase because the release commit hash changes.
Step 10: GitHub Release (latest only)
For latest releases, create a GitHub release:
gh release create cli-X.Y.Z --generate-notes --title "cli-X.Y.Z"
Step 11: Install + verify locally
npm i -g happy@{channel}
happy --version
happy daemon status
Report the installed version and daemon status.
The smoke check must confirm that happy --version matches the published version, not just npm metadata. If it reports the old version, rebuild after the version bump and cut a corrective patch release.
Mobile Release
Package: packages/happy-app
Variants: development, preview, production
Platform: Expo SDK 54 / React Native 0.81.4
Build types
Always ask the user explicitly what they want to release. Present these
options in order of popularity:
- OTA update (preview) — push JS bundle to preview channel. Most common release type.
- OTA update (production) — push JS bundle to production channel. Do this after preview OTA is validated.
- Native dev build — when native code changes. Points to dev server with bundled app.
- Full native release — build all profiles (dev + preview + production) to prep for a new native release.
OTA Updates
pnpm --filter happy-app run ota
pnpm --filter happy-app run ota:production
OTA scripts require a message — stdin is not readable from Claude Code, so run the
underlying eas update directly with --message:
cd packages/happy-app && APP_ENV=preview NODE_ENV=preview tsx sources/scripts/parseChangelog.ts && pnpm typecheck && eas update --branch preview --message "<message>"
Native Builds
-
Dev build — development profile, used when native code changes (points to dev server)
cd packages/happy-app && eas build --profile development --platform all --non-interactive
-
TestFlight / Play Store builds — use -store profiles for distribution via TestFlight and Play Store.
Always pass --auto-submit so the build goes straight to TestFlight after completion.
cd packages/happy-app && eas build --profile preview-store --platform ios --non-interactive --auto-submit
cd packages/happy-app && eas build --profile development-store --platform ios --non-interactive --auto-submit
cd packages/happy-app && eas build --profile production --platform ios --non-interactive --auto-submit
IMPORTANT: Always pass --non-interactive to eas build commands. Without it,
EAS prompts for Apple account login interactively which breaks in non-TTY contexts
(Claude Code, CI). Remote credentials are already configured on EAS servers.
IMPORTANT: Always pass --auto-submit to -store builds. Without it, the build
finishes but never reaches TestFlight — you have to manually submit with eas submit.
EAS Build Profiles
Profile Distribution Channel Notes
development-store store development Dev build via TestFlight
preview-store store preview TestFlight / Play Store internal testing
production store production App Store / Play Store submission
Internal / ad-hoc profiles (rarely used)
These install via direct link, NOT TestFlight. Almost never needed — prefer
the -store profiles above.
Profile Distribution Channel
development internal development
preview internal preview
Version source is remote (EAS manages build numbers, auto-incremented).
Runtime version "20" — bump when native code changes to invalidate OTA.
App Store Connect
Apple ID: steve@bulkovo.com
Team ID: 466DQWDR8C
App Store Connect App IDs:
Production: 6748571505 (com.ex3ndr.happy)
Preview: 6749025570 (com.slopus.happy.preview)
Development: 6748984254 (com.slopus.happy.dev)
Web Release
Package: packages/happy-app (same Expo app, web export)
Dockerfile: Dockerfile.webapp
Image: docker.korshakov.com/happy-app:{version}
K8s: packages/happy-app/deploy/happy-app.yaml (3 replicas)
Web releases go through TeamCity (Lab_HappyWeb). The config is in the TeamCity UI, not in the repo.
Flow: expo export --platform web -> nginx:alpine static serve -> Docker build -> push -> K8s deploy.
Build args: POSTHOG_API_KEY, REVENUE_CAT_STRIPE.
Guide the user to trigger the TeamCity build, or help with manual Docker builds if needed.
Server Release
Package: packages/happy-server
Dockerfile: Dockerfile.server (production), Dockerfile (standalone w/ PGlite)
Image: docker.korshakov.com/handy-server:{version}
K8s: packages/happy-server/deploy/handy.yaml (1 replica, port 3005)
Server releases go through TeamCity (Lab_HappyServer). The config is in the TeamCity UI, not in the repo.
Build: node:20 + python3 + ffmpeg, builds happy-wire + happy-server.
Secrets from Vault: handy-db, handy-master, handy-github, handy-files, handy-e2b, handy-revenuecat, handy-elevenlabs.
Redis: happy-redis StatefulSet (redis:7-alpine, 1Gi persistent volume).
Guide the user to trigger the TeamCity build.
Docs Release
Site: happy.engineering (GitHub Pages)
Repo: github.com/slopus/slopus.github.io
Separate repo, not part of this monorepo. Guide the user to push to that repo.
Rules
- Always present options — never assume which component, channel, or version.
- Always verify before publishing — show the user what will be published and get confirmation.
- Unit tests are the gate, not integration tests — integration tests are slow and have flaky abort/interrupt tests.
- Use pnpm publish, not npm publish — avoids workspace protocol issues.
- Use --ignore-scripts — we build and test explicitly, no need for prepublishOnly to redo it.
- Never force-push tags — if a tag exists, stop and ask.