Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# Releasing the CLI
Running daemons self-update from a public GCS bucket (`src/lib/selfUpdate.ts`). Releasing is pushing a
tag: `.github/workflows/release.yml` bundles the CLI, publishes it to that bucket, and cuts the GitHub
Release. **The tag IS the version — CI never bumps.**
Tags are `vX.Y.Z_cli`, not plain `vX.Y.Z`: the backend now lives in this same repo under `backend/`
and releases as `vX.Y.Z_backend` (see `../backend/README.md`), so the suffix is what tells each tag-push
trigger which workflow to run. It's stripped before anything treats it as a version — the published
version everywhere below (manifest, release title, `ADAPTER_VERSION`) is always a bare `X.Y.Z`.
```bash
make release-cli # bump the patch, tag, push — CI does the rest
make release-cli ARGS="--dry-run" # print the version it would cut and the notes, do nothing
make release-cli ARGS="0.2.0" # release an explicit version
make release-cli ARGS="--notes-file notes.md" # hand-written release notes
```
Nothing is built locally and no GCS credentials are needed: the only things `cli/scripts/release-cli.sh`
touches are git and a public HTTPS read of the manifest. See [`../docs/cicd.md`](../docs/cicd.md) for
what the release workflow itself does, step by step.
**Why the version comes from two places.** `cli/scripts/release-cli.sh` takes the highest of the last
git tag and the version published under the `cli` key of `harness/cli/metadata.json`. The two can
drift — a scratch publish that slipped, or a manifest edited by hand, leaves the manifest ahead of the
last tag. Bumping from tags alone there would produce a version LOWER than what's already served —
every daemon refuses it (`shouldUpdate` / `semverGt` in `src/lib/selfUpdate.ts`) while the release
still reports success.
Unlike `autonomous-harness-desktop`'s `make release`, there is no `--minor` flag: `shouldUpdate` is a
plain "strictly newer" check with no major/minor-triggers-a-forced-update concept, so every release
just needs to outrank what's live.
`package.json`'s `version` field is never touched by this script; the published version is baked into
the bundle via `ADAPTER_VERSION` at build time (see "The publishing step" below).
## The publishing step
`cli/scripts/upload-cli.sh` is the publishing step, and CI is its only caller: `release.yml` runs it
with the version taken from the tag. There is deliberately no `make` target for it — publishing from a
laptop created no git tag and bumped from the remote manifest, so the repo stopped reflecting what was
served, and a stale local tag then broke the next `make release-cli` at `git fetch --tags`. If CI is
down, fix CI; a version with no tag is worse than a late one.
What the script does, for reading the workflow:
1. Reads the current version from the live manifest (falling back to `package.json` if the manifest is
unreadable), then bumps or takes the version it was given.
2. Bundles with `ADAPTER_VERSION="$VERSION" npm run bundle` and asserts `node dist/cli.js version`
reads back that exact string before uploading anything.
3. Uploads `cli.js` and `notify.mjs` to `harness/cli/<version>/`, then download-merge-reuploads
`metadata.json` in a single write, touching only the `cli` key.
A failed build stops the release; nothing is uploaded and no version is consumed.
## The `curl | bash` installer
`cli/scripts/install.sh` is what `curl -fsSL https://cdn.autonomous.ai/harness/cli/install.sh | bash`
runs: it installs the managed Node runtime and the CLI bundle from the same manifests the release
publishes to. It is a static file, versioned by nothing, and does **not** ship with a release — publish
it on its own whenever it changes:
```bash
make upload-cli-install-sh # cli/scripts/install.sh -> harness/cli/install.sh in the bucket
curl -fsSL https://cdn.autonomous.ai/harness/cli/install.sh | head -5 # confirm the CDN edge moved
```
Its command contract (flags, exit codes, what it refuses) is pinned by `cli/src/scripts/install.spec.ts`,
which runs with the rest of `make cli-test`. Not to be confused with `cli/scripts/install-cli.sh`, the
local dev loop below.
## Local install (no upload)
`bash cli/scripts/install-cli.sh` bundles **this working tree** into `~/.harness/cli` — the same
layout the public installer produces — and restarts the daemon on it, so `harness` on this machine
means your code without publishing anything:
```bash
make install-cli # bundle + install + restart the daemon
make install-cli ARGS="--no-restart" # skip restarting the daemon
make install-cli ARGS="--no-updates" # pin self-update off (see below)
```
Self-update stays **on** by default: the local build is labelled with the currently published version,
which the release you're level with cannot outrank, so your bytes survive until a genuinely newer
version ships — then the daemon quietly swaps back to it. `--no-updates` pins your build instead.
## GCS layout and manifest shape
```
gs://s3-autonomous-upgrade-3/harness/cli/metadata.json
gs://s3-autonomous-upgrade-3/harness/cli/<version>/cli.js
gs://s3-autonomous-upgrade-3/harness/cli/<version>/notify.mjs
```
The manifest is always read straight off the GCS origin (`CDN_ASSET_BASE_URL` in `upload-cli.sh` does
not apply to it) — it's polled every ~60s by every running daemon, and this product's CDN caps any
cacheable response at ~31 days regardless of origin headers, so it must never be CDN-fronted. The bundle
files it points at are the opposite: immutable once published, so their `url` fields point at
`cdn.autonomous.ai` instead, and are uploaded with a long `Cache-Control` on purpose.
```json
{
"cli": {
"version": "0.1.72",
"url": "https://cdn.autonomous.ai/harness/cli/0.1.72/cli.js",
"sha256": "<64 hex>",
"size": 2103552
}
}
```
The bucket must be public-read — that is bucket policy, not something the scripts set.
## How a running daemon self-updates
1. Polls `ADAPTER_UPDATE_URL` every `ADAPTER_UPDATE_CHECK_MS` (default 60 s) — see `src/config/env.ts`.
2. Compares the manifest's `cli.version` against its own — strictly newer only (`semverGt`), so
republishing an old build can never downgrade a fleet.
3. Downloads `cli.js` + `notify.mjs` and verifies each sha256/size against the manifest **before**
anything is swapped in. A mismatch is discarded.
4. Swaps the bundle in when the daemon is idle and restarts, with rollback on a bad build.
**There is no undo.** A bad release is rolled back by publishing a *higher* version containing the
older code — never by deleting the new one, which just leaves every daemon pointed at a 404. Disable
self-update on one machine with `ADAPTER_UPDATE_DISABLE=true`.
## Rolling out safely
Publish to a scratch manifest before touching the real one, then point a local build at it:
```bash
gh workflow run release.yml -f version=9.9.9 -f metadata_path=harness/cli/metadata-test.json
```
```bash
ADAPTER_UPDATE_URL=https://storage.googleapis.com/s3-autonomous-upgrade-3/harness/cli/metadata-test.json harness start
```
No daemon polls `metadata-test.json`, so nothing self-updates from it. The *artifacts* still go to
their real home (`harness/cli/<version>/` is hardcoded in `upload-cli.sh`), so use an unmistakable
version and delete `gs://s3-autonomous-upgrade-3/harness/cli/9.9.9/` afterwards.
279918da1This file is pinned to the selected release. Download the original to open it in your design tool.