# How to Set Up CI/CD for OpenWork Development: A Complete Guide

> Learn to set up CI/CD for OpenWork development with GitHub Actions. Install Node 24, pnpm 11.4, Bun, and OpenCode CLI. Run tests, builds, and publish commands seamlessly.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-14

---

**To set up CI/CD for OpenWork development, configure the GitHub Actions workflows under `.github/workflows/`, install the exact toolchain—Node 24, pnpm 11.4, Bun, and the OpenCode CLI—and run the same test, build, and publish commands defined in [`ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/ci-tests.yml) and the specialized package pipelines.**

This guide explains how to set up CI/CD for OpenWork development using the existing automation in the `different-ai/openwork` repository. The project defines its entire delivery cycle under `.github/workflows/`, ensuring every push or pull request to the `dev` branch is linted, tested, and built before it reaches production.

## How OpenWork CI/CD Is Structured

All workflow definitions live in the `.github/workflows/` directory on the `dev` branch. The primary entry point for continuous integration is [`.github/workflows/ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-tests.yml), which triggers on every push and pull request.

Several focused workflows complement the main pipeline:

- **[`ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/ci-tests.yml)** — Installs Node 24, pnpm 11.4, Bun, and the OpenCode CLI; caches the pnpm store; and executes unit, integration, and end-to-end tests across workspace packages.
- **[`ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/ci-openwork-ui-mcp.yml)** — Lints, type-checks, and performs a dry-run publish for the `openwork-ui-mcp` package.
- **[`ci-i18n.yml`](https://github.com/different-ai/openwork/blob/main/ci-i18n.yml)** — Extracts and validates internationalization strings.
- **[`ci-no-new-eval-flows.yml`](https://github.com/different-ai/openwork/blob/main/ci-no-new-eval-flows.yml)** — Blocks accidental additions of new eval flow files.
- **[`ci-enterprise-mcp-mock.yml`](https://github.com/different-ai/openwork/blob/main/ci-enterprise-mcp-mock.yml)** — Verifies the enterprise-MCP mock implementation when present.
- **[`release-macos-aarch64.yml`](https://github.com/different-ai/openwork/blob/main/release-macos-aarch64.yml)** and **[`release-daytona-snapshot.yml`](https://github.com/different-ai/openwork/blob/main/release-daytona-snapshot.yml)** — Build and sign production binaries or Docker images.

Together these files provide a complete CI/CD cycle for OpenWork development.

## The Core Test Pipeline in [`ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/ci-tests.yml)

The primary workflow triggers on every push or pull request to the `dev` branch. It begins by installing the required toolchain, including the OpenCode CLI whose version is read from [`constants.json`](https://github.com/different-ai/openwork/blob/main/constants.json). The job then caches the pnpm store using keys derived from [`pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/pnpm-lock.yaml) and [`evals/pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/evals/pnpm-lock.yaml) before executing tests.

### Build, Test, and Packaging Stages

The CI run follows a strict five-stage sequence:

1. **Build and lint** — Verify syntax and run static analysis.
2. **Unit tests** — Run package-level suites for `@openwork/app`, `openwork-server`, `@openwork/desktop`, and the eval runner.
3. **Integration and end-to-end tests** — Execute `pnpm --filter @openwork/app test:e2e`.
4. **Packaging** — Build server plugins exactly as the production bundle does via `pnpm --filter openwork-server build`.
5. **Publish readiness** — When a Git tag matching `openwork-ui-mcp-v*` is pushed, the corresponding workflow publishes to npm using the `NPM_TOKEN` secret.

This sequence ensures that code merged into `dev` is linted, tested, and buildable before it reaches users.

## Specialized and Release Workflows

Beyond the main pipeline, dedicated workflows enforce quality gates and manage releases.

### Lint and Publish Checks for `openwork-ui-mcp`

The [`.github/workflows/ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-openwork-ui-mcp.yml) file targets the `openwork-ui-mcp` package specifically. It runs `node --check`, performs a type-check, and executes `npm publish --dry-run` on every relevant change. If the commit is tagged with a version string like `openwork-ui-mcp-v*`, the workflow performs a real publish to the npm registry.

### Guardrails and i18n Validation

The [`.github/workflows/ci-i18n.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-i18n.yml) workflow extracts translation keys and fails if i18n rules are violated. Meanwhile, [`.github/workflows/ci-no-new-eval-flows.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-no-new-eval-flows.yml) acts as a guardrail by ensuring no eval flow files are added unintentionally, keeping the repository’s evaluation surface stable.

### Production Release Automation

Release workflows such as [`.github/workflows/release-macos-aarch64.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/release-macos-aarch64.yml) handle signing and packaging for macOS on Apple Silicon, while [`.github/workflows/release-daytona-snapshot.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/release-daytona-snapshot.yml) produces Docker snapshots. These jobs run outside the standard `dev` branch PR flow and are triggered by release tags or manual dispatch.

## Mirror the OpenWork CI Environment Locally

To reproduce the exact environment used by the GitHub Actions runners, run the same installation and verification steps locally. This is the fastest way to debug a failing job before pushing to the `dev` branch.

```bash

# 1. Clone the repository and checkout the dev branch

git clone https://github.com/different-ai/openwork.git
cd openwork
git checkout dev

# 2. Install the exact Node version (24) – you can use nvm or asdf

nvm install 24 && nvm use 24

# 3. Install pnpm (matches the CI version)

npm i -g pnpm@11.4.0

# 4. Install the OpenCode CLI (the same binary the CI downloads)

#    The version is read from constants.json; the script below mirrors the CI logic.

OPENCODE_GITHUB_REPO=${OPENCODE_GITHUB_REPO:-anomalyco/opencode}
VERSION=$(node -p "require('./constants.json').opencodeVersion.replace(/^v/, '')")
ARCH=$(uname -m)
case "$(uname -s)" in
  Linux)  [[ $ARCH == aarch64 || $ARCH == arm64 ]] && ASSET=opencode-linux-arm64.tar.gz || ASSET=opencode-linux-x64-baseline.tar.gz ;;
  Darwin) [[ $ARCH == arm64 ]] && ASSET=opencode-darwin-arm64.zip || ASSET=opencode-darwin-x64-baseline.zip ;;
  *) echo "Unsupported OS"; exit 1 ;;
esac
curl -L -o opencode.$ASSET "https://github.com/${OPENCODE_GITHUB_REPO}/releases/download/v${VERSION}/${ASSET}"

# Extract and put the binary on PATH (same as CI)

mkdir -p $HOME/.opencode/bin
if [[ $ASSET == *.tar.gz ]]; then tar -xzf opencode.$ASSET -C $HOME/.opencode/bin; else unzip -q opencode.$ASSET -d $HOME/.opencode/bin; fi
export PATH=$HOME/.opencode/bin:$PATH

# 5. Verify the CLI

opencode --version

# 6. Install Bun (used by some scripts)

curl -fsSL https://bun.sh/install | bash
export PATH=$HOME/.bun/bin:$PATH
bun --version

# 7. Install project dependencies (cached in CI)

pnpm install --frozen-lockfile --prefer-offline

# 8. Install the eval workspace (same as CI)

pnpm --dir evals install --frozen-lockfile --prefer-offline

# 9. Run the same test lanes as CI

pnpm --dir evals run spec                    # eval specs

pnpm --filter @openwork/app test             # app unit tests

pnpm --filter openwork-server test           # server unit tests

pnpm --filter @openwork/desktop test         # desktop unit tests

pnpm test:eval-runner                        # eval-runner unit tests

pnpm check:outbound-access                    # outbound-access manifest check

pnpm --filter openwork-server build           # build server plugins (type-check)

pnpm --filter @openwork/desktop typecheck:electron
pnpm --filter @openwork/app test:e2e        # e2e tests

```

Running these commands reproduces the same environment and verification steps executed on GitHub Actions. If a test fails locally, it will fail in CI, allowing you to fix issues before opening a pull request.

## Customize and Extend Your OpenWork CI/CD

You can tune the pipeline to match evolving project needs without breaking the existing flow.

- **Update Node or pnpm versions** — Edit the `node-version` fields in [`.github/workflows/ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-tests.yml), [`.github/workflows/ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-openwork-ui-mcp.yml), and related files, then bump your local environment with `nvm` or `asdf`.
- **Add a new workspace package to CI** — Append a `pnpm --filter <pkg>` command to the job steps in [`ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/ci-tests.yml), or create a new workflow that follows the pattern of [`.github/workflows/ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-openwork-ui-mcp.yml).
- **Adjust caching logic** — The pnpm store cache key is based on [`pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/pnpm-lock.yaml) and [`evals/pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/evals/pnpm-lock.yaml). If you introduce a new lock file, include it in the cache key hash in the workflow YAML.
- **Trigger an npm release** — Push a Git tag that matches the `openwork-ui-mcp-v*` pattern. The [`ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/ci-openwork-ui-mcp.yml) workflow will detect the tag and publish the package using the repository secret `NPM_TOKEN`.

## Summary

- OpenWork CI/CD is orchestrated through GitHub Actions under `.github/workflows/`, with [`.github/workflows/ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-tests.yml) serving as the main pipeline for the `dev` branch.
- The CI toolchain requires **Node 24**, **pnpm 11.4**, **Bun**, and the **OpenCode CLI**, whose version is pinned in [`constants.json`](https://github.com/different-ai/openwork/blob/main/constants.json).
- The pipeline covers build, unit tests, integration tests, end-to-end tests (`pnpm --filter @openwork/app test:e2e`), server plugin builds (`pnpm --filter openwork-server build`), and npm publishing.
- Specialized workflows enforce i18n compliance, prevent new eval flows, and handle macOS and snapshot releases.
- You can replicate the entire CI environment locally by running the installation and test commands exactly as defined in the workflows.

## Frequently Asked Questions

### What versions of Node, pnpm, and Bun does the OpenWork CI use?

The OpenWork CI runners currently use **Node 24**, **pnpm 11.4**, and the latest **Bun** release installed via the official install script. These versions are hard-coded in [`.github/workflows/ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-tests.yml) and [`.github/workflows/ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-openwork-ui-mcp.yml), so local development should match them exactly to avoid lock file or runtime discrepancies.

### How can I reproduce a failing OpenWork CI job on my machine?

Clone the `dev` branch, install the toolchain using the same commands found in [`.github/workflows/ci-tests.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-tests.yml), and run the test lane that failed. The bash script above downloads the identical OpenCode CLI binary and executes `pnpm install --frozen-lockfile --prefer-offline` followed by the relevant `pnpm --filter` test and build commands.

### Which Git tag format triggers npm publishing in OpenWork?

Pushing a tag that matches the pattern **`openwork-ui-mcp-v*`** triggers the publish job inside [`.github/workflows/ci-openwork-ui-mcp.yml`](https://github.com/different-ai/openwork/blob/main/.github/workflows/ci-openwork-ui-mcp.yml). The workflow first runs `npm publish --dry-run` on every commit, but only performs the live publish to npm when it detects a matching tag and the `NPM_TOKEN` secret is available.

### Where does OpenWork CI store its cache keys?

The pnpm store is cached using keys derived from the contents of **[`pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/pnpm-lock.yaml)** and **[`evals/pnpm-lock.yaml`](https://github.com/different-ai/openwork/blob/main/evals/pnpm-lock.yaml)**. If either lock file changes, the cache is invalidated and dependencies are reinstalled. Adjust the `actions/cache` step in the workflow files if you add additional lock files to the repository.