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

t3code uses a single GitHub Actions workflow in .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. 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 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【.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 to produce a unified latest-mac.yml (or 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 with --github-output to capture whether any package.json files actually changed
  3. Re-formats modified 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 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 Synchronizes version strings across apps/server, apps/desktop, apps/web, and packages/contracts. Updates each package.json and reports whether changes occurred. Supports --github-output for CI consumption.

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


# 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) 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 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.
  • 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 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:

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 script synchronizes version strings across the monorepo. It walks apps/server, apps/desktop, apps/web, and packages/contracts, updating each 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):

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →