# Updating the CLI and the server

Source: https://codeherder.com/docs/updating/

Update ch manually, plan a self-hosted server upgrade and its maintenance window, see how ch and the device server stay current, and fix a stuck update.

There are two `ch` binaries to keep in mind: the one installed on your own machine (used for the CLI), and the one inside each running device server. Both can keep themselves current, and this page covers how. Not installed yet? See [Quickstart](https://codeherder.com/docs/quickstart/) for the one-line install command.

## Check your current version

```
ch --version
```

`ch version` and `ch -v` do the same thing. This prints the installed version (and build commit for stamped releases).

## Update manually

Run `ch self-update` to download and install the latest release in one step:

```
ch self-update
```

> **Note:** `ch update` is not a valid command. If you type it by mistake, the CLI will suggest `ch self-update` for you.

CodeHerder downloads the new binary for your platform, verifies it by SHA-256, and atomically replaces your existing `ch` binary. If you are already on the latest version, the command says so and exits.

**Updating your machine’s `ch` does not update running device servers.** Each device server manages its own binary independently. After a successful update, `ch` prints a reminder: “Any running `ch device-server` will auto-restart on its next update check.”

### Check for an update without applying it

```
ch self-update --check
```

Reports the installed version versus the latest available, then exits with code 0 if you are up to date or non-zero if a newer version is available. No changes are made to your binary. Unlike a plain `ch self-update`, `--check` reports this even from an unstamped dev build — it never needs `--force`.

This exit-code contract makes it straightforward to use in CI or a cron job:

```
# Example: daily cron that alerts when ch is behind
# 0 9 * * * /usr/local/bin/ch self-update --check || echo "ch update available — run ch self-update"
ch self-update --check
if [ $? -ne 0 ]; then
  echo "ch is behind the latest release."
fi
```

Add `--json` to get the same result as a machine-readable `current` / `available` / `needsUpdate` / `updated` object instead of exit-code-only — see [Using the ch CLI](https://codeherder.com/docs/using-the-cli/) for the JSON conventions every command shares.

### Update a build made from source

Building `ch` from source gives you one of two binaries, and they behave differently:

- An **unstamped dev build** carries no version at all. `ch self-update` refuses to replace it by default — pass `--force` to override: `ch self-update --force`
- A **local build** carries a version like `2.3.1+7`. It doesn’t need `--force`: the moment a newer release ships, `ch self-update` replaces it and `ch self-update --check` reports it as behind. What neither will do is call it behind a release still sitting at the same version number — a local build is always treated as caught up with its own version, never behind it.

## Your `ch` keeps itself current too

Auto-update is on by default for the `ch` you run on your own machine, the same way it is for the device server, when a release source is configured (`CH_CLI_MANIFEST_URL`, or the manifest URL stamped into your `ch` build; with neither it stays off). It works in two parts: a quick stderr notice when your version and the server’s fall out of step, in either direction, and a preflight that updates before certain commands run.

**The notice.** Any time the CLI talks to the server, it compares versions and prints one line to stderr when they differ. Which direction you’re off in changes the wording:

```
ch (build 2.3.0) is behind the server (build 2.3.1). Run: ch self-update
```

```
ch (build 2.3.1) is ahead of the server (build 2.3.0). The server may be running a stale deploy.
```

The numbers are build versions, which decide updates. On a hosted install the build is the hosted release, not the CodeHerder version. When your `ch` knows its CodeHerder version and it differs from the build, the line names it too, for example `ch (build 11.1.274, CodeHerder 12.19.11)`. `ch self-update` messages add the CodeHerder version of the new build, and say `unchanged` when only the hosted wrapper changed.

Behind is the common case: your `ch` hasn’t picked up a release yet, and `ch self-update` fixes it. Ahead usually means a self-hosted server needs restarting on the release it’s meant to be running — see [Confirm which build is live](https://codeherder.com/docs/self-hosting/#confirm-which-build-is-live) for how to check. The ahead line specifically never appears against a server that hasn’t been assigned a real release version, so it can’t mistake an unreleased build for a stale deploy.

Either line goes to stderr, not stdout, so it never breaks a script that pipes `--json` output, and by default each repeats at most once an hour.

**The preflight.** `ch start` and `ch session attach` check for a newer release before they do anything else. If one is available, `ch` downloads it, replaces itself, and restarts the same command on the new version:

```
ch: updated build 2.3.0 → 2.3.1, restarting `ch start`.
```

No other command runs this preflight — `ch self-update` is still there whenever you want to check or update by hand. If the preflight’s check fails — no network, no build for your platform, a failed download — `ch` just keeps running the version you already have. You’ll never see an error from it.

The preflight needs a real terminal on standard input, so it stays out of the way of scripts and CI by default. To opt a non-interactive caller in, set `CH_AUTO_UPDATE_NONINTERACTIVE=1`. A build made from source, dev or local, never runs the preflight and never prints the notice — see [Update a build made from source](https://codeherder.com/docs/updating/#update-a-build-made-from-source) above.

### Tuning or disabling your CLI’s auto-update

| Setting | Default | Description |
| --- | --- | --- |
| `--no-auto-update` | on | Disable the preflight for this one command. |
| `CH_AUTO_UPDATE=0` | on | Disable the preflight for every command. |
| `CH_AUTO_UPDATE_NONINTERACTIVE=1` | off | Let the preflight run without a terminal on standard input — for scripts and CI. |
| `CH_AUTO_UPDATE_INTERVAL=<duration>` | `1h` | How often the preflight checks for a newer release, and how often the notice can repeat. |
| `CH_UPDATE_NOTICE=0` | on | Silence the stderr notice, in either direction. |

## The device server keeps itself current automatically

When you run `ch device-server`, auto-update is on by default once a release source is configured (`CH_CLI_MANIFEST_URL`, or the build stamp; with neither it stays off). Roughly once an hour, the server checks whether a newer `ch` release is available. When it finds one, it downloads and verifies the binary and then restarts itself with the new version.

**By default, in-flight agent sessions are not interrupted.** The device server detaches any running agent processes before it restarts, and the new process adopts them automatically. Tasks continue without any action from you. Start the device server with `--kill-agents-on-shutdown` (or set `CH_DS_KILL_ON_STOP=1`) if you’d rather it stop running agents on any shutdown or restart — including one triggered by auto-update — instead of carrying them across.

**Scope:** auto-update keeps only the device server’s own `ch` binary current — it does not change the agent harness (for example, the Claude Code installation the agent uses to do its work).

### If a device is stuck on an old version

A few situations commonly strand a device behind the latest release. None of them need you to watch closely — each is either self-healing or a one-line fix:

- **A `ch` built from source never auto-updates.** This covers both shapes: an unstamped dev build, and a local build carrying a version like `2.3.1+7`. Either way, the device server’s auto-updater skips its check entirely, and the **Version current** readiness check (see [Managing your devices](https://codeherder.com/docs/devices/#readiness-checks)) reports healthy rather than warning — there’s no separate signal telling you it’s behind. Install a released `ch` binary on that machine, or run `ch self-update` (add `--force` only on a dev build), then restart the device server.
- **Restarting the device server does not, by itself, pull a newer version in immediately.** The auto-updater’s first check happens a full interval after startup, not right away — so on the default hourly cadence, a fresh restart can be up to an hour away from checking. To move a device onto a new version right now: run `ch self-update` (add `--force` on a dev build) to replace the binary on disk, then restart `ch device-server` — it starts up already on the new version.
- **The update needs write access to the directory the `ch` binary lives in**, since both manual and automatic updates replace it in place. If `ch` is installed somewhere only another account can write to, a manual `ch self-update` reports the failure directly; the device server’s background updater instead logs a warning and quietly retries at the next check. Install `ch` somewhere your own user owns, or run the update as the account that owns the install directory.
- **A failed check is not something to worry about.** A transient failure — a network blip, or a brief mismatch right after a new release goes out — is retried automatically, several times, within the same check window. If every retry in that window still fails, the device server keeps running its current binary and tries again at the next interval; no session is lost and nothing needs your attention.
- **A laptop that’s asleep through a check doesn’t miss its turn.** The device server tracks wall-clock time rather than a fixed timer, so it picks the check back up within about a minute of the machine waking.
- **On a proxied network,** both `ch self-update` and the device server’s auto-updater fetch releases over HTTPS and honor the standard `HTTPS_PROXY` and `NO_PROXY` environment variables in whatever environment the command runs in. `HTTP_PROXY` has no effect here, since the download itself is always HTTPS.

### Tuning or disabling the device server’s auto-update

Pass these flags when starting the device server:

| Flag | Default | Description |
| --- | --- | --- |
| `--auto-update=false` | on | Disable automatic updates entirely. |
| `--auto-update-interval <duration>` | `1h` | How often to check for a newer version. |

The same settings are available as environment variables — convenient when the device server runs as a background service:

| Variable | Description |
| --- | --- |
| `CH_DS_AUTO_UPDATE=0` | Disable automatic updates. |
| `CH_DS_AUTO_UPDATE_INTERVAL=<duration>` | Override the check cadence (e.g. `6h`). |

Examples:

```
ch device-server --auto-update-interval 6h   # check every six hours
ch device-server --auto-update=false         # never auto-update
```

For the full `ch device-server` reference, see [Managing your devices](https://codeherder.com/docs/devices/).

## Upgrade the server and plan maintenance

This section is for the person who runs a self-hosted server. The steps to upgrade are in [Upgrade to a new release](https://codeherder.com/docs/self-hosting/#upgrade-to-a-new-release). This section covers when to do it and what to tell your users.

A new release applies its schema migrations when the server starts. The server answers `503 migrating` until they finish. Most migrations take well under a second. A migration can lock a table or run for minutes on a large database. Each release tells you which.

**Read the notice before you upgrade.**

1. Read the `migrations` object in the release manifest. It names each migration the release adds since the previous release. It says whether any may lock a table (`mayLock`) or run long (`mayRunLong`). See [Read the manifest](https://codeherder.com/docs/self-hosting/#read-the-manifest).
2. Run the preflight with the new binary against your own database. It writes nothing: `codeherder migration-notice -db "$DSN "` It lists each migration your database has not applied. It flags each one that may lock or run long. For each flagged table it prints the size, so you can judge the duration. Add `-strict` to make the command exit 1 when anything is flagged.
3. If you skip releases, the manifest of the newest release is not the full list. The preflight is the full answer. It reads your own ledger.

**Choose the maintenance window.**

- If nothing is flagged, upgrade at a time you accept a short restart.
- If anything is flagged, schedule a maintenance window and tell your users before it starts. Take a database backup first. Plan for the migration time on your own data, not on a small test database.
- While the server reports `migrating` at `/v1/health`, wait. Do not stop the server with SIGTERM or restart it. A restart abandons the migration and starts it again from the beginning.
- A failed upgrade rolls back by restoring the backup. That loses the data written after the backup. See [Roll back a failed upgrade](https://codeherder.com/docs/self-hosting/#roll-back-a-failed-upgrade).

You choose the window and you tell your users. CodeHerder sets no deadline for an upgrade, except the upgrade-by date on a security release.

### Rule text for approval

This text is a template. Adapt it to your policy.

> **Migration notice rule.**
>
> 1. Every server release manifest states its migrations and whether any may lock a table or run long. The release pipeline generates the statement. No person writes it.
> 2. When a release adds a migration that may lock or run long, the support contact tells the customer communication owner, through ______, within ______ of the release. The email names the migration, the table, the expected effect and any measured duration.
> 3. A migration that would hold a lock for minutes on a large table ships with a separate operator step. It never runs as a startup statement.
> 4. The customer chooses the maintenance window and tells its own users. CodeHerder sets no deadline, except the upgrade-by date in the security release notice rule.

## Related guides

- [Managing your devices](https://codeherder.com/docs/devices/) — device health and troubleshooting an Offline device
- [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) — tune auto-update under a service manager
- [Monitoring your agents](https://codeherder.com/docs/monitoring-agents/) — watch fleet health after an update
- [Self-hosted deployment](https://codeherder.com/docs/self-hosting/) — upgrade a self-hosted server, and confirm which build is live
