This document covers the unified release workflow for stable and nightly desktop releases.
- Workflow:
.github/workflows/release.yml - Triggers:
- push tag matching
v*.*.*for stable releases - scheduled nightly at
09:00 UTC - manual
workflow_dispatchfor either channel
- push tag matching
- Runs quality gates first: lint, typecheck, test.
- Builds four artifacts in parallel for both channels:
- macOS
arm64DMG - macOS
x64DMG - Linux
x64AppImage - Windows
x64NSIS installer
- macOS
- Publishes one GitHub Release with all produced files.
- Stable tags with a suffix after
X.Y.Z(for example1.2.3-alpha.1) are published as GitHub prereleases. - Only plain stable
X.Y.Zreleases are marked as the repository's latest release. - Nightly runs are always GitHub prereleases and never marked latest.
- Automatically generated release notes are pinned to the previous tag in the same channel, so stable compares to the previous stable tag and nightly compares to the previous nightly tag.
- Stable tags with a suffix after
- Includes Electron auto-update metadata (for example
latest*.yml,nightly*.yml, and*.blockmap) in release assets. - Publishes the CLI package (
apps/server, npm packaget3) with OIDC trusted publishing from the same workflow file:- stable releases publish npm dist-tag
latest - nightly releases publish npm dist-tag
nightly
- stable releases publish npm dist-tag
- Deploys the hosted web app to Vercel only after a release is published:
- stable releases are aliased to the
latesthosted app channel - nightly releases are aliased to the
nightlyhosted app channel
- stable releases are aliased to the
- Signing is optional and auto-detected per platform from secrets.
The hosted app is intentionally not deployed by Vercel's Git integration. The
web project disables automatic Git deployments in apps/web/vercel.ts via
git.deploymentEnabled: false, and .github/workflows/release.yml deploys the
web app with Vercel CLI after the GitHub Release succeeds.
Required GitHub Actions secrets:
VERCEL_TOKENVERCEL_ORG_IDVERCEL_PROJECT_ID
Optional GitHub Actions variables:
VERCEL_TEAM_SLUG: overrides the Vercel CLI scope when the team slug is preferred over theVERCEL_ORG_IDsecret.T3CODE_WEB_ROUTER_URL: defaults tohttps://app.t3.codes.T3CODE_WEB_LATEST_DOMAIN: defaults tolatest.app.t3.codes.T3CODE_WEB_NIGHTLY_DOMAIN: defaults tonightly.app.t3.codes.
Required Vercel domains:
app.t3.codes: the stable router domain users open.latest.app.t3.codes: channel alias updated by stable releases.nightly.app.t3.codes: channel alias updated by nightly releases.
The router domain uses apps/web/vercel.ts routes. Users opt into a channel by
visiting /__t3code/channel?channel=latest or
/__t3code/channel?channel=nightly; the router stores the
t3code_web_channel cookie and rewrites future requests on app.t3.codes to
the matching channel alias.
The release deploy job rewrites release package versions before upload so the
hosted app's About panel renders the release version. It also passes
VITE_HOSTED_APP_CHANNEL=latest|nightly, which renders the hosted update track
selector in the About panel. Changing the selector navigates through
/__t3code/channel on the router domain so the user's channel cookie is updated
before redirecting to the hosted app root.
One-time Vercel dashboard setup:
- Confirm the web project root directory remains
apps/web. - Add the three domains above to the web project.
- Disable automatic Git deployments in the dashboard if desired; the committed
vercel.tssetting is the source-of-truth, but disconnecting Git in the dashboard is also safe. - Promote or alias one deployment containing the router rules in
apps/web/vercel.tstoapp.t3.codesonce. Future release jobs should only update the channel aliases.
- Workflow:
.github/workflows/release.yml - Triggers:
- scheduled every day at
09:00 UTC - manual
workflow_dispatchwithchannel=nightly
- scheduled every day at
- Runs the same desktop quality gates and artifact matrix as the tagged release flow.
- Publishes a GitHub prerelease only:
- tag format:
nightly-vX.Y.Z-nightly.YYYYMMDD.<run_number> - release name includes the short commit SHA
make_latestis alwaysfalse
- tag format:
- Uses the next stable patch version as the nightly base. For example,
0.0.17produces nightlies on0.0.18-nightly.*. - Publishes Electron auto-update metadata to the dedicated
nightlyupdater channel, so desktop users can opt into that track independently from stable. - Publishes the CLI package (
apps/server, npm packaget3) to thenightlynpm dist-tag using the same nightly version. - Does not commit version bumps back to
main.
- Runtime updater:
electron-updaterinapps/desktop/src/main.ts. - Update UX:
- Background checks run on startup delay + interval.
- No automatic download or install.
- The desktop UI shows a rocket update button when an update is available; click once to download, click again after download to restart/install.
- Provider: GitHub Releases (
provider: github) configured at build time. - Repository slug source:
T3CODE_DESKTOP_UPDATE_REPOSITORY(formatowner/repo), if set.- otherwise
GITHUB_REPOSITORYfrom GitHub Actions.
- Temporary private-repo auth workaround:
- set
T3CODE_DESKTOP_UPDATE_GITHUB_TOKEN(orGH_TOKEN) in the desktop app runtime environment. - the app forwards it as an
Authorization: Bearer <token>request header for updater HTTP calls.
- set
- Required release assets for updater:
- platform installers (
.exe,.dmg,.AppImage, plus macOS.zipfor Squirrel.Mac update payloads) - channel metadata:
latest*.ymlfor stable releases,nightly*.ymlfor nightly releases *.blockmapfiles (used for differential downloads)
- platform installers (
- macOS metadata note:
electron-updaterreadslatest-mac.ymlon stable andnightly-mac.ymlon nightly, for both Intel and Apple Silicon.- The workflow merges the per-arch mac manifests into one channel-specific mac manifest before publishing the GitHub Release.
The workflow publishes the CLI with npm publish from apps/server after bumping
the package version to the release tag version.
Checklist:
- Confirm npm org/user owns package
t3(or rename package first if needed). - In npm package settings, configure Trusted Publisher:
- Provider: GitHub Actions
- Repository: this repo
- Workflow file:
.github/workflows/release.yml - Environment (if used): match your npm trusted publishing config
- Ensure npm account and org policies allow trusted publishing for the package.
- Create release tag
vX.Y.Zand push; workflow will:- set
apps/server/package.jsonversion toX.Y.Z - build web + server
- run
npm publish --access public --tag latest
- set
- Nightly runs from the same workflow file publish with
npm publish --access public --tag nightly.
Use this first to validate the release pipeline.
- Confirm no signing secrets are required for this test.
- Create a test tag:
git tag v0.0.0-test.1git push origin v0.0.0-test.1
- Wait for
.github/workflows/release.ymlto finish. - Verify the GitHub Release contains all platform artifacts.
- Download each artifact and sanity-check installation on each OS.
Required secrets used by the workflow:
CSC_LINKCSC_KEY_PASSWORDAPPLE_API_KEYAPPLE_API_KEY_IDAPPLE_API_ISSUER
Checklist:
- Apple Developer account access:
- Team has rights to create Developer ID certificates.
- Create
Developer ID Applicationcertificate. - Export certificate + private key as
.p12from Keychain. - Base64-encode the
.p12and store asCSC_LINK. - Store the
.p12export password asCSC_KEY_PASSWORD. - In App Store Connect, create an API key (Team key).
- Add API key values:
APPLE_API_KEY: contents of the downloaded.p8APPLE_API_KEY_ID: Key IDAPPLE_API_ISSUER: Issuer ID
- Re-run a tag release and confirm macOS artifacts are signed/notarized.
Notes:
APPLE_API_KEYis stored as raw key text in secrets.- The workflow writes it to a temporary
AuthKey_<id>.p8file at runtime.
Required secrets used by the workflow:
AZURE_TENANT_IDAZURE_CLIENT_IDAZURE_CLIENT_SECRETAZURE_TRUSTED_SIGNING_ENDPOINTAZURE_TRUSTED_SIGNING_ACCOUNT_NAMEAZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAMEAZURE_TRUSTED_SIGNING_PUBLISHER_NAME
Checklist:
- Create Azure Trusted Signing account and certificate profile.
- Record ATS values:
- Endpoint
- Account name
- Certificate profile name
- Publisher name
- Create/choose an Entra app registration (service principal).
- Grant service principal permissions required by Trusted Signing.
- Create a client secret for the service principal.
- Add Azure secrets listed above in GitHub Actions secrets.
- Re-run a tag release and confirm Windows installer is signed.
- Ensure
mainis green in CI. - Bump app version as needed.
- Create release tag:
vX.Y.Z. - Push tag.
- Verify workflow steps:
- preflight passes
- all matrix builds pass
- release job uploads expected files
- Smoke test downloaded artifacts.
- macOS build unsigned when expected signed:
- Check all Apple secrets are populated and non-empty.
- Windows build unsigned when expected signed:
- Check all Azure ATS and auth secrets are populated and non-empty.
- Build fails with signing error:
- Retry with secrets removed to confirm unsigned path still works.
- Re-check certificate/profile names and tenant/client credentials.