# Release Process for t3code: Complete Guide to Automated CI/CD

> Learn the t3code release process. Automate your CI/CD pipeline with a single GitHub Actions workflow for builds, publishing, and release creation triggered by tags or manual dispatch.

- Repository: [Ping.gg/t3code](https://github.com/pingdotgg/t3code)
- Tags: how-to-guide
- Published: 2026-04-18

---

**t3code uses a single GitHub Actions workflow in [`.github/workflows/release.yml`](https://github.com/pingdotgg/t3code/blob/main/.github/workflows/release.yml) that automates the entire release pipeline—including pre-flight checks, multi-platform desktop builds, npm publishing, and GitHub release creation—triggered by version tags, scheduled cron jobs, or manual dispatch.**

The **t3code** repository (hosted at `pingdotgg/t3code`) maintains a fully automated release process that handles both stable and nightly distributions. This guide examines the complete CI/CD pipeline, from quality gates to final version bumps, based on the actual implementation in the source code.

## Workflow Overview and Triggers

The release orchestration lives entirely in [`.github/workflows/release.yml`](https://github.com/pingdotgg/t3code/blob/main/.github/workflows/release.yml). This single workflow definition handles three distinct trigger types under the `on:` section【.github/workflows/release.yml#L4-L15】:

- **Tag push** (`v*.*.*`): Initiates stable releases when you push a semantic version tag
- **Scheduled cron** (`0 */3 * * *`): Runs every 3 hours to check for changes and potentially generate a new nightly build
- **Workflow dispatch**: Allows manual triggering via the GitHub UI with inputs for `channel` (stable/nightly) and explicit version strings

## Pre-flight Checks and Version Resolution

The `preflight` job runs quality gates before any artifacts are built. It executes `bun run lint`, `bun run typecheck`, and `bun run test` to ensure code integrity【.github/workflows/release.yml#L54-L61】.

For version handling, the workflow distinguishes between release channels:

- **Stable releases**: Validates the incoming tag matches semantic versioning (supporting `X.Y.Z` or prerelease formats) and extracts version components【.github/workflows/release.yml#L31-L45】
- **Nightly releases**: Invokes [`scripts/resolve-nightly-release.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/resolve-nightly-release.ts) to generate deterministic version strings like `0.0.18-nightly.20240917.3` based on the current date, run number, and short commit SHA【scripts/resolve-nightly-release.ts#L46-L60】

The resolved metadata (channel, version, tag, ref) is exported as job outputs for downstream consumption【.github/workflows/release.yml#L71-L80】.

## Building Desktop Artifacts

The `build` job uses a matrix strategy to compile native desktop installers across four platform/architecture combinations:

| Platform | Target | Architecture |
|----------|--------|--------------|
| macOS | `dmg` | `arm64` |
| macOS | `dmg` | `x64` |
| Linux | `AppImage` | `x64` |
| Windows | `nsis` | `x64` |

(Windows arm64 support exists in the configuration but is currently commented out.)【.github/workflows/release.yml#L71-L100】

Each matrix entry performs the following steps:

1. Checks out the exact commit referenced by `needs.preflight.outputs.ref`
2. Installs dependencies via **Bun**
3. Aligns all package versions using [`scripts/update-release-package-versions.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/update-release-package-versions.ts)【.github/workflows/release.yml#L26-L28】
4. **Optional code signing**: macOS binaries are signed using Apple Developer secrets, while Windows uses Azure Trusted Signing. If secrets are absent, the build proceeds unsigned with a console notice【.github/workflows/release.yml#L79-L89】
5. Invokes the desktop build command: `bun run dist:desktop:artifact` with `--platform`, `--target`, and `--arch` parameters【.github/workflows/release.yml#L71-L107】
6. Collects output files (`*.dmg`, `*.zip`, `*.AppImage`, `*.exe`, `*.blockmap`, `*.yml`) into a `release-publish` directory and uploads them as build artifacts named `desktop-<platform>-<arch>`【.github/workflows/release.yml#L109-L152】

## Publishing the CLI Package

The `publish_cli` job handles npm distribution after successful builds. It:

1. Checks out the release commit
2. Re-runs the version alignment script to ensure the CLI package recognizes the correct version
3. Builds the CLI filtering for `@t3tools/web` and `t3` workspaces
4. Publishes the `t3` npm package using **OIDC trusted publishing** via `node apps/server/scripts/cli.ts publish` with the appropriate dist-tag (`latest` for stable, `nightly` for nightly)【.github/workflows/release.yml#L84-L88】

## Creating the GitHub Release

The `release` job aggregates artifacts from all platform builds. It:

1. Downloads all `desktop-*` artifacts using `actions/download-artifact`
2. Merges macOS updater manifests using [`scripts/merge-update-manifests.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/merge-update-manifests.ts) to produce a unified [`latest-mac.yml`](https://github.com/pingdotgg/t3code/blob/main/latest-mac.yml) (or [`nightly-mac.yml`](https://github.com/pingdotgg/t3code/blob/main/nightly-mac.yml))【.github/workflows/release.yml#L22-L44】
3. Uses **softprops/action-gh-release** to publish the release with:
   - The tag from `needs.preflight.outputs.tag`
   - Name formatted as `T3 Code vX.Y.Z`
   - Automatic release notes (GitHub compares against the previous tag)
   - Correct `prerelease` and `make_latest` flags based on channel
   - All built artifacts and updater metadata【.github/workflows/release.yml#L69-L108】

The job handles the "first release" scenario (no previous tag) separately from subsequent releases when constructing the release payload.

## Finalizing the Release

After a **stable** release completes, the `finalize` job performs repository maintenance:

1. Creates a GitHub App token to attribute the subsequent push to the automation bot
2. Runs [`scripts/update-release-package-versions.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/update-release-package-versions.ts) with `--github-output` to capture whether any [`package.json`](https://github.com/pingdotgg/t3code/blob/main/package.json) files actually changed
3. Re-formats modified [`package.json`](https://github.com/pingdotgg/t3code/blob/main/package.json) files using `oxfmt`
4. Refreshes the `bun.lock` lockfile
5. Commits the version bump (e.g., `chore(release): prepare v1.2.4`) and pushes to `main`【.github/workflows/release.yml#L110-L87】

This ensures the source repository remains in sync with the released version.

## Supporting Scripts

Two TypeScript utilities underpin the release logic:

| Script | Purpose |
|--------|---------|
| [`scripts/resolve-nightly-release.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/resolve-nightly-release.ts) | Calculates nightly version metadata. Generates strings like `0.0.18-nightly.20240917.3` using the base version, current date, GitHub run number, and short commit SHA. Can be run locally for debugging. |
| [`scripts/update-release-package-versions.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/update-release-package-versions.ts) | Synchronizes version strings across `apps/server`, `apps/desktop`, `apps/web`, and `packages/contracts`. Updates each [`package.json`](https://github.com/pingdotgg/t3code/blob/main/package.json) and reports whether changes occurred. Supports `--github-output` for CI consumption. |

Both scripts are Effect-TS programs that can be executed locally:

```bash

# Generate nightly metadata

node scripts/resolve-nightly-release.ts \
  --date 20240917 \
  --run-number 3 \
  --sha $(git rev-parse --short HEAD)

# Bump all package versions to 2.0.0

node scripts/update-release-package-versions.ts 2.0.0

```

## Summary

- **t3code** uses a unified GitHub Actions workflow ([`.github/workflows/release.yml`](https://github.com/pingdotgg/t3code/blob/main/.github/workflows/release.yml)) triggered by version tags, scheduled cron jobs, or manual dispatch.
- The **preflight** job enforces quality gates (lint, typecheck, tests) and resolves version metadata using [`scripts/resolve-nightly-release.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/resolve-nightly-release.ts) for nightlies or tag parsing for stable releases.
- A **matrix build** compiles desktop artifacts for macOS (arm64/x64), Linux (x64), and Windows (x64), with optional code signing and version alignment via [`scripts/update-release-package-versions.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/update-release-package-versions.ts).
- The **publish_cli** job distributes the `t3` npm package using OIDC trusted publishing with appropriate dist-tags (`latest` or `nightly`).
- The **release** job aggregates artifacts, merges macOS updater manifests, and publishes GitHub Releases using `softprops/action-gh-release`.
- The **finalize** job bumps version strings in [`package.json`](https://github.com/pingdotgg/t3code/blob/main/package.json) files and pushes the commit back to `main` after stable releases.

## Frequently Asked Questions

### How do I manually trigger a nightly release?

You can dispatch the workflow manually from the GitHub Actions tab or via the CLI. Specify `channel: nightly` to force a nightly build regardless of the cron schedule:

```bash
gh workflow run release.yml -f channel=nightly

```

The workflow will check if the `main` branch has changed since the last nightly tag. If changes exist, it generates a new nightly version (e.g., `0.0.18-nightly.20240917.3`) and builds all artifacts.

### What happens if code signing secrets are missing?

The build proceeds unsigned. During the `build` job, the workflow attempts to configure macOS signing (Apple Developer secrets) and Windows signing (Azure Trusted Signing). If the required secrets are not present in the repository, the scripts print a notice and continue building unsigned binaries. This ensures forks and pull requests can still produce testable artifacts.

### How does the workflow handle versioning across multiple packages?

The [`scripts/update-release-package-versions.ts`](https://github.com/pingdotgg/t3code/blob/main/scripts/update-release-package-versions.ts) script synchronizes version strings across the monorepo. It walks `apps/server`, `apps/desktop`, `apps/web`, and `packages/contracts`, updating each [`package.json`](https://github.com/pingdotgg/t3code/blob/main/package.json) to the target release version. This runs before building to ensure the desktop app and CLI report consistent version numbers, and runs again during the `finalize` job to commit the bumped versions back to the repository.

### Can I publish a stable release without creating a tag first?

No. Stable releases require a version tag to trigger the workflow. You must create and push a tag matching the `v*.*.*` pattern (or just `*.*.*` which the workflow validates):

```bash
git tag 1.2.3
git push origin 1.2.3

```

The `preflight` job validates this tag, extracts the version components, and proceeds with the full release pipeline. Without a tag push, the workflow will only run if manually dispatched with explicit parameters, but standard stable releases are tag-driven.