How to cut a new desktop release of Cumora.
# 1. Bump the version in package.json
npm version patch # → 0.1.0 → 0.1.1 (creates the tag locally)
# 2. Push the tag — GitHub Actions does the rest
git push origin main --tagsThe push to a v* tag fires the desktop release workflow:
.github/workflows/release.ymlin this repo → dispatches toyetone/cumora-releases, which builds + signs + publishes the Electron app for macOS (arm64 + Intel), Windows, and Linux. Final artifacts land at https://github.com/yetone/cumora-releases/releases.
It does not deploy the API server. Backend production deploys are an explicit, separately approved action; a desktop tag must never silently mutate the backend.
The auto-updater in the desktop app reads from cumora-releases, so
once the release workflow finishes (~15–20 minutes), running clients
will pick it up on their next periodic update check.
Every push to main runs .github/workflows/build.yml. Before publishing an
image it must pass both TypeScript projects, the big-brain and tracked-LLM
guards, unit tests, and the Postgres/Redis integration suite. A successful run
produces immutable SHA-tagged server (and, when affected, agent-computer)
images. It does not deploy them.
To deploy a candidate:
- Open Actions → Deploy → Run workflow.
- Enter the exact short SHA tag produced by Build. Avoid
latestwhen a SHA is available; Deploy resolves either tag to a digest before touching GKE. - Set
include_agent=Ywhen the build changedserver/src/agents/**, the bundled CLI/runtime, or the agent-computer image. Otherwise useN. - Approve the protected
productionenvironment. The approver should not be the person who built the feature for high-risk changes. - Verify the workflow summary contains the selected digest, previous server image, completed rollout, and passed authenticated smoke.
Deploy first proves that the existing production API and smoke credential are
healthy, runs one candidate-image migration Job and verifies its immutable
ledger/index gates, records the current revision as the rollback baseline,
updates the server (and optionally agent runtime) by digest, waits for GKE, then
exercises real authenticated tenant paths: auth, conversations, and the
Shipping overview/schema. A migration failure leaves the Deployment untouched;
a failed post-deploy smoke automatically runs
kubectl rollout undo, waits for the old revision to become ready, and fails
the workflow.
Shipping features additionally track a production readback deadline, default
24 hours after a successful release. The Ship workspace surfaces due items;
the server turns missed deadlines into overdue release state plus high
severity friction. .github/workflows/production-readback.yml independently
checks authenticated production paths each day. A feature only reaches
Learned after its production release has explicit readback evidence and no
failing regression asset.
On yetone/cumora:
| Name | Purpose |
|---|---|
GCP_WIF_PROVIDER |
Workload Identity Federation provider used to resolve and deploy images. |
GCP_DEPLOY_SA |
Least-privilege service account for Artifact Registry and the production GKE deployment. |
CUMORA_SMOKE_TOKEN |
Dedicated, revocable session/service token used only for authenticated smoke/readback. |
CUMORA_SMOKE_COMPANY_ID |
Non-sensitive tenant id that the smoke identity belongs to. |
Protect the production GitHub environment with required reviewers. Put the
smoke secrets in both production and production-readback (or configure the
latter to inherit repository secrets). Rotate the smoke token like any other
production credential and never print it in workflow output.
- Matrix-builds the Electron app on four runners (macOS arm64, macOS Intel, Windows, Linux).
- On macOS, imports the Developer ID cert into a temporary keychain, signs the app bundle, and notarises via the Apple credentials in GitHub Secrets.
- Uploads platform-specific artifacts (DMG, ZIP, EXE, AppImage, DEB,
latest*.ymlautoupdate feeds, blockmaps). - Merges the per-arch
latest-mac.ymlfiles so one feed advertises both arm64 and Intel. - Generates a user-friendly changelog via the OpenAI API from the commit list between the previous tag and this one.
- Mirrors everything to the
cumora-updatesCloudflare R2 bucket (only when R2 secrets are configured — optional). - Creates the GitHub Release with the artifacts attached and the generated changelog as the body.
- Posts an announcement to the Discord release channel (only when the webhook is configured — optional).
On yetone/cumora:
| Name | Purpose |
|---|---|
RELEASES_REPO_TOKEN |
Fine-grained PAT scoped to yetone/cumora-releases. Needs Actions: write. |
On yetone/cumora-releases:
| Name | Purpose |
|---|---|
CUMORA_REPO_TOKEN |
Fine-grained PAT scoped to yetone/cumora. Needs Contents: read. |
MAC_CERTIFICATE_P12 |
Base64-encoded Developer ID Application cert (.p12). base64 -i Certificates.p12 | pbcopy. |
MAC_CERTIFICATE_PASSWORD |
Password protecting the .p12 above. |
APPLE_ID |
Apple Developer account email. |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password for notarisation. Generated at appleid.apple.com → Sign-In and Security → App-Specific Passwords. |
APPLE_TEAM_ID |
10-char Team ID from developer.apple.com → Account → Membership. |
OPENAI_API_KEY |
Used to generate the changelog. |
R2_ACCESS_KEY_ID |
(optional) R2 mirror for the cumora-updates bucket. |
R2_SECRET_ACCESS_KEY |
(optional) R2 mirror credential. |
CLOUDFLARE_ACCOUNT_ID |
(optional) R2 endpoint scope. |
DISCORD_RELEASE_WEBHOOK_URL |
(optional) Discord channel webhook for release announcements. |
If any of the optional secrets are unset, the workflow skips that step and still succeeds.
build.publishinpackage.jsonpoints atyetone/cumora-releases, so the in-app auto-updater knows where to look.build.mac.notarize.teamIdreadsAPPLE_TEAM_IDfrom the workflow environment.build/entitlements.mac.plistdeclares the hardened-runtime entitlements Electron needs (JIT, network access, dyld vars).
If a previous release needs a re-roll (signing failed, missing artifact,
etc.), use the workflow_dispatch form on yetone/cumora-releases:
- Go to https://github.com/yetone/cumora-releases/actions/workflows/release.yml
- Run workflow → enter:
ref= the tag from this repo (e.g.v0.1.0)version= the bare version (e.g.0.1.0)
- The workflow re-builds and overwrites the existing release artifacts.
- macOS notarisation fails. Most commonly
APPLE_APP_SPECIFIC_PASSWORDwas rotated or the cert is expired. Checkhttps://appleid.apple.comandKeychain Accesson a Mac with the cert installed. - Build runs but no GitHub Release is created. The publish job
requires
permissions: contents: writewhich is already set in the workflow. If you forked the repos, make sure that permission is also granted on your fork. latest-mac.ymlmentions only one architecture. One of the two Mac runners failed before producing the yml. Look at theUpload build artifactsstep onbuild-mac-arm64/build-mac-x64.- The desktop app doesn't see the update. The autoupdater polls
every 10 minutes (default for
electron-builder). Force it from the app menu or restart.