Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
442335385This file is pinned to the selected release. Download the original to open it in your design tool.
name: CI
# Modelled on autonomous-harness-desktop's ci.yml. Like that one, this runs ON DEMAND only.
#
# It used to run on every pull request as well (2026-09-10 → 2026-09-16). That was switched off
# deliberately: the suite is timing-sensitive on the shared ubuntu runner (real tmux server, shell
# scripts, sqlite3 polls), so a red check on a PR said more about the runner than about the change,
# and every contributor had to read the log to find out which. The verdict on a branch is still one
# click away — Actions -> CI -> Run workflow, pick the branch — which is what a reviewer does when
# they want it. If per-PR CI comes back, use `pull_request`, never `pull_request_target`: a fork's PR
# must run with a read-only token and no secrets, since this job executes the contributor's code.
#
# There is no `push` trigger either. Nobody pushes to main directly (CONTRIBUTING.md step 2) and
# merges are squashed, so a push run would re-test a commit somebody already ran by hand.
on:
# The only way this runs on its own: Actions -> CI -> Run workflow, on any branch.
workflow_dispatch:
# Callable as a reusable workflow. release.yml used to gate on this and take the Node pin from
# its output; it no longer does (see the note above `publish` there) and pins the same version
# itself, so the output below is kept only for any future caller.
workflow_call:
outputs:
node-version:
description: The Node the suite ran on. release.yml bundles the release with it.
value: ${{ jobs.typecheck-test.outputs.node-version }}
permissions:
contents: read
concurrency:
group: cli-ci-${{ github.workflow }}-${{ github.ref }}
# Every run here was started by a person or by a release, and is the record of whether that commit
# was green — never killed to make room for a newer one.
cancel-in-progress: false
jobs:
# Typecheck, the full suite, and the package updater's complete-coverage gate.
typecheck-test:
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
node-version: ${{ steps.pin.outputs.node-version }}
defaults:
run:
working-directory: cli
steps:
- uses: actions/checkout@v7
# THE runtime pin — release.yml's `publish` job carries the same value; bump them together.
#
# It is not a taste in Node versions: it is the version of the MANAGED runtime the shipped CLI
# actually executes on. `harness/runtime/metadata.json` in the release bucket serves
# node v22.23.2 for darwin-arm64, darwin-x64, linux-x64 and linux-arm64, `install.sh` unpacks
# it under ~/.harness/runtime, and the launcher names that binary absolutely — so the user's
# own node, if any, never runs this bundle. Testing on anything else measures a runtime no
# machine in the field has. Bump this together with `make upload-node-runtime` in
# autonomous-harness-desktop, never on its own.
#
# (package.json's `engines` says >=20, which is the floor a THIRD PARTY may run it on. A floor
# is not a pin: a floating 22.x turns an unrelated upstream release into a red build on an
# untouched commit.)
- id: pin
run: echo "node-version=22.23.2" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v7
with:
node-version: ${{ steps.pin.outputs.node-version }}
cache: npm
# Relative to the repo root, not to `defaults.working-directory` — this is an action
# input, not a `run:`.
cache-dependency-path: cli/package-lock.json
# TWO lockfiles are tracked here, and only one of them has ever been guarded.
#
# `package-lock.json` is the one the released bytes are built from — every build path in this
# repo shells out to `npm run bundle` (install-cli.sh, upload-cli.sh, remote-machine.sh) — and
# `npm ci` below refuses to run when it disagrees with package.json, so npm's half checks
# itself.
#
# `pnpm-lock.yaml` is checked by nothing, and on 2026-09-09 that cost a maintainer an
# afternoon: `bonjour-service` was added to package.json with only package-lock.json updated,
# main went green, and `make install-cli` then died at `Could not resolve "bonjour-service"`
# on every machine whose node_modules came from pnpm. Deleting the file would also fix that;
# keeping it and checking it is the choice made here, because contributors do use it.
#
# `--lockfile-only` resolves without installing (~5s) and writes nothing under --frozen-lockfile.
# Verified against this repo: it exits 1 on the stale lockfile that shipped in c4de318, naming
# the added dependency, and 0 once it is regenerated.
- name: Check pnpm-lock.yaml still matches package.json
run: |
npm i -g pnpm@11 >/dev/null
pnpm install --frozen-lockfile --lockfile-only
# `npm ci`, not `npm install`: the lockfile is the input, not a suggestion.
- run: npm ci
- run: npx tsc --noEmit
# 1,871 tests over 144 files, ~30s. The four `*.real.spec.ts` / `localE2e.spec.ts` suites that
# need a live tmux, Herdr or Cursor are gated behind RUN_* env vars and stay skipped here —
# `npm run test:tmux-real` and friends are how those are run, on a machine that has them.
- run: npx vitest run
- name: Require complete harness updater coverage
run: npm run test:dsh-updates