Skip to content

Updating & Upgrading ​

How you upgrade depends on how you installed in the first place. The three forms have completely different upgrade paths, and taking the wrong one doesn't fail loudly — it leaves two competing installations on the machine.

What you installedWho upgrades itHow
Standalone CLI (npm)Younpm update -g @yoooclaw/cli
Standalone CLI (native binary)YouRe-run install.sh (with --force) / install.ps1 (with -Force)
Hermes pluginThe plugin itself (can be triggered from chat)Ask it to self-update in conversation, or re-run the plugin installer
OpenClaw pluginThe host / the plugin itselfopenclaw plugins update @yoooclaw/phone-notifications or openclaw ntf update

Plugin users: don't upgrade the CLI separately

The Hermes plugin bundles the platform's yoooclaw executable, with the version pinned by the manifest.json inside the plugin package. Running the CLI's own install.sh puts a second CLI in ~/.local/bin at a different version from the one under the plugin-managed path — the plugin keeps using its own copy while your terminal uses the newly installed one, and the two may disagree about the same storage. Upgrading the plugin is upgrading the CLI; it neither needs nor should be done separately.

Standalone CLI ​

Step one: check your version and channel ​

bash
yoooclaw update self

It only checks, never installs — it reads the npm registry's dist-tags, compares versions, and tells you which command to run. Returned fields:

FieldMeaning
distWhere this binary came from, npm or native. Determined by whether the executable path sits under node_modules
current / latestLocal version / latest version on the channel
channellatest; pass --beta to query the beta dist-tag instead
updateAvailableCompares only the x.y.z triple, ignoring prerelease suffixes
commandThe upgrade command when one is available, already matched to dist; null when you're up to date
bash
yoooclaw update self --beta --format json

The npm channel ​

bash
npm update -g @yoooclaw/cli      # stable
npm i -g @yoooclaw/cli@beta      # prerelease

The npm package is a thin Node launcher; the actual Go binary is pulled per-platform through optionalDependencies, so upgrading the npm package replaces the platform binary along with it.

The native binary channel ​

macOS / Linux:

bash
# upgrade to the latest stable
curl -fsSL https://artifact.yoooclaw.com/cli/install.sh | sh -s -- --force

# upgrade to a specific version (the only way to get a prerelease)
curl -fsSL https://artifact.yoooclaw.com/cli/install.sh \
  | sh -s -- --version 0.12.0 --force

Windows (PowerShell):

powershell
& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/cli/install.ps1))) -Version 0.12.0 -Force

Upgrades require --force / -Force

When a yoooclaw already exists at the target path the installer errors out (… 已存在;用 --force 覆盖,或先卸载旧版本 — "already exists; use --force to overwrite, or uninstall the old version first"). That guard exists to prevent accidental overwrites. A first install doesn't need it; an upgrade always does. The native upgrade command returned by yoooclaw update self already includes --force / -Force, so you can copy it verbatim.

--beta has been removed

install.sh --beta from older docs now fails outright. Without --version the installer only resolves stable releases; prereleases must be named explicitly, e.g. --version 0.13.0-beta.0, and publishing a prerelease no longer overwrites the live install script or the latest marker. The npm channel's @beta dist-tag is unaffected.

Two more upgrade-relevant installer behaviors:

  • The install directory is re-derived, not remembered (prefer ~/.local/bin, else a writable /usr/local/bin). If you originally installed elsewhere with --dir, pass the same --dir again when upgrading, or you'll end up with a second copy in the default location.
  • Login autostart is registered on upgrade: when upgrading from an older version that had no autostart preference, the installer registers daemon login autostart for the initialized active profile (even if the daemon happened to be stopped); an explicit autostart disable, or the Hermes plugin currently owning storage, is left as is.
  • systemd is probed first on Linux: when upgrading from somewhere without a login-session environment (e.g. an Agent shell), the installer confirms it can reach the current user's systemd before stopping the daemon (recovering the session environment from /run/user/<uid> if needed); if it can't, it keeps the old daemon and binary and aborts the update, rather than stopping a daemon it can't bring back.
  • PATH is left untouched by default: the installer only writes the install directory into your shell config when you pass --modify-path, and that write is idempotent (if the line is already there, it just reports "PATH already configured"). Without --modify-path it only installs the binary and leaves your shell config alone — true for both first installs and upgrades. --no-modify-path is kept only for backward compatibility with older invocations and is equivalent to the default.

On macOS / Linux the native upgrade command from update self points at the GitHub-hosted installer script (on Windows, at artifact.yoooclaw.com/cli/install.ps1). If GitHub is slow for you, swap in artifact.yoooclaw.com (Alibaba Cloud OSS) as shown above — the two scripts take identical flags and differ only in where artifacts are downloaded from.

Don't mix the two channels ​

Install once via npm and once via install.sh and you have two yoooclaw binaries; whichever comes first on PATH wins, and update self only ever sees the one currently running. To genuinely switch channels, remove the old one first:

bash
yoooclaw uninstall              # stop the daemon + remove binaries and config, keep data
npm uninstall -g @yoooclaw/cli  # required for the npm form, which node_modules owns

uninstall keeps notification / recording / image data by default, so your data survives a channel switch and reinstall.

Ownership handling during an upgrade ​

If both the CLI and the Hermes plugin are installed on the same machine, install.sh / install.ps1 preserves whatever owned ownership before the upgrade by default — whoever held the writer lock / Relay connection before still does after, an upgrade never silently hands it off. The installer stops any running standalone CLI daemon first (if there is one), swaps the binary, then restarts it under its original profile, rolling back and restarting automatically on failure. Only passing --activate / -Activate explicitly (or setting YOOOCLAW_ACTIVATE_OWNER=cli) hands ownership to the standalone CLI after install; you can also do the same handoff manually at any time with yoooclaw owner activate cli — see Command Reference · owner.

WUYING cloud desktops ​

Environments installed with install-wuying.sh are upgraded by re-running the same script; with existing credentials --api-key can be omitted, and --force overwrites both the binary and the installed Skills (including the CLI absolute path written into them):

bash
curl -fsSL https://artifact.yoooclaw.com/cli/install-wuying.sh | sh -s -- --skill claude --force

Refresh Skills after upgrading ​

Skills are embedded in the CLI binary and copied into the Agent directory at install time, so they don't update with the CLI automatically. After upgrading, run yoooclaw skills install --force (it also removes merged old Skills such as yoooclaw-lightrule-create), then restart the Agent session.

Restart the daemon after upgrading ​

Replacing the binary does not affect an already-running daemon — that's an old-version process, still happily receiving data. To make the new version take effect:

bash
yoooclaw daemon restart
yoooclaw daemon status --format json    # confirm `version` changed

Know the difference: rotating an api-key calls for daemon reload (incrementally refreshes tunnels without dropping live connections); replacing the binary or rotating the gateway token requires daemon restart — reload isn't enough.

Hermes plugin ​

The plugin package embeds the CLI binary, so "upgrading the plugin" swaps the plugin and the CLI in one step. Plugin 0.10.0 bundles CLI 0.11.0-beta.0 (which adds the cloud todo commands).

The plugin exposes two tools so you can upgrade while chatting with Hermes:

ToolWhat it does
plugin_update_checkReads the OSS latest channel marker and compares versions. Results are cached for 5 minutes; force=true bypasses the cache
plugin_update_applyActually performs the upgrade. Requires confirm=true, otherwise it returns YOOOCLAW_UPDATE_NOT_CONFIRMED

The confirm=true gate is deliberate: the model can't decide to upgrade on its own — it only passes that argument after you explicitly agree in the conversation. An optional version argument pins the target.

What plugin_update_check returns:

json
{ "ok": true, "currentVersion": "0.7.0", "latestVersion": "0.7.1",
  "channel": "latest", "updateAvailable": true,
  "updating": false, "editableInstall": false }

The full flow once you confirm:

text
1. Write pending.json (from/to versions, originating chat, start time)
2. Download the rendered install.sh from OSS
3. Spawn the installer with setsid, detached — it must outlive the gateway restart it triggers
4. The tool returns "started" immediately, so that reply reaches you before the restart
5. A watcher thread in the old process streams update.log into the chat
6. The installer finishes → hermes gateway restart
7. The reloaded plugin calls resume_after_reload() and pushes the final ✅/❌

Neither the setsid in step 3 nor the watcher in step 5 is redundant: if the installer fails during pip, the gateway never restarts, so there is no "new process" to report the outcome — only the old process's watcher can surface that failure. And the pending.json → result.json handoff is an atomic rename, guaranteeing the old process's watcher and the new process's resume path can never both notify.

If no push channel is available, the result waits for your next message and is delivered by the pre_llm_call hook rather than being silently dropped.

Option B: re-run the installer ​

Same command as the first install; running it again upgrades:

bash
curl -fsSL https://artifact.yoooclaw.com/hermes-plugin/install.sh | bash -s -- --api-key "ock_..."

Windows (PowerShell):

powershell
& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/hermes-plugin/install.ps1))) --api-key ock_...

Unlike the CLI's installer, the plugin installer needs no --force — it runs pip install --upgrade, where overwriting is the point. --api-key is also optional; omit it and the existing key in ~/.yoooclaw/credentials.json is reused.

Upgrade-relevant options:

OptionDescription
--version <VERSION>Install a specific version (resolved to the matching wheel URL on the OSS channel)
--oss-channel <NAME>Which version marker to read when --version is omitted; defaults to latest
--oss-base-url <URL>Point at a different OSS artifact source
--no-ossSkip OSS and fall back to the pip package (pip install yoooclaw-hermes-plugin)
--no-restartDon't run hermes gateway restart afterwards (you must restart yourself for it to take effect)
--python <PATH>Explicitly name the Python of the venv Hermes uses
--hermes-profile <name>If your Hermes runs a named profile (hermes --profile <name>), pass this so the installer patches that profile's config.yaml instead of the unused global config; the env var HERMES_PROFILE has the same effect

After installing, the installer runs an activation verification pass (confirming the package is installed → the current Hermes profile's plugins.enabled is checked → the runtime actually holds the writer lock and Relay connection). If verification fails, the script now fails loudly with a non-zero exit code (previously it printed "installation complete" regardless of whether verification passed); the messaging distinguishes "installed but not activated" from "hard failure" — don't treat a non-zero exit as success, and see Troubleshooting below.

Four cases that get refused ​

CodeTriggerWhat to do
YOOOCLAW_UPDATE_EDITABLE_INSTALLThe plugin is a pip install -e editable install or a source treeSelf-update would clobber the working tree you're editing, so it refuses outright; upgrade manually in the repo
YOOOCLAW_UPDATE_IN_PROGRESSAn update is already runningWait for it. If pending.json outlives the timeout (default 1800s, tunable via YOOOCLAW_HERMES_UPDATE_TIMEOUT_SECONDS) it's treated as stale and a new update is allowed through
YOOOCLAW_UPDATE_NOT_NEWERThe target isn't newer than what's installedNothing to do. Self-update never downgrades; use the installer's --version to install an older release
YOOOCLAW_UPDATE_BAD_VERSIONMalformed version number, or the channel marker returned something invalidCheck the --version spelling; if the channel misbehaves, check whether --oss-base-url points somewhere wrong

Verify after upgrading ​

bash
hermes yoooclaw lifecycle status

Check three things: that components.pluginVersion is the new version, that daemon.storage.claimed is true (the new process reclaimed the storage writer lock), and that generation changed along with the plugin version. The plugin version is one of the generation's inputs, so an upgrade necessarily changes the generation and triggers one tunnel rebuild — that's expected.

Don't run yoooclaw update self in plugin mode

The CLI under the plugin-managed path isn't inside node_modules, so update self classifies it as native and suggests running the CLI's install.sh — doing so installs a second CLI that floats free of the plugin. The embedded CLI's version is governed by the plugin package's manifest.json and can only be changed by upgrading the plugin.

OpenClaw plugin ​

Standard OpenClaw upgrades through the host:

bash
openclaw plugins update @yoooclaw/phone-notifications

You can also use the plugin's own openclaw ntf update (or ask the Agent to call the plugin-update tool). Both paths now reuse the host's native installer instead of having the plugin's built-in updater replace the directory:

  • Standard OpenClaw: probes openclaw plugins install --help, checks current install ownership with plugins info --json, then calls the host's native install; install registration, $include and enabled state are all left to the host.
  • Older hosts (e.g. 5.28's legacy scan-install protocol), no host CLI found, or non-standard hosts: fall back to the install script, which installs production dependencies, then backs up and replaces the plugin.
  • If native capability probing errors or the native install fails, it reports the failure rather than bypassing host policy with a direct overwrite; Nix mode refuses self-update.
  • When the new version's manifest contract, entry points and bins match the enabled old version exactly, existing capability grants carry over automatically; new capabilities, or anything it can't verify, return CAPABILITY_CONSENT_REQUIRED — review the host report, then run openclaw ntf update --accept-capabilities to grant explicitly.

Updates triggered inside the Gateway run in a separate process so they don't block the Relay heartbeat. The install log is at <stateDir>/extensions/.phone-notifications-update/<updateId>/install.log, and the result is last-update.json in the same root (the subdirectory's install-result.json records the actual install directory and native errors), so they survive npm version-directory switches. Windows keeps the manual stop/start mode: stop the Gateway, wait for status=succeeded in last-update.json, then start it.

Since 1.17.0 the plugin also reports the start, success and failure of every automatic / manual update plus a running-version heartbeat to the server, queuing locally and retrying on network errors; since 1.16.2 it verifies after an upgrade that the systemd gateway is actually active and shows the live connection state, catching "upgrade finished but the service never came up" early.

Switching to the new update flow for the first time

This behavior only applies after a version containing it is running — older updaters aren't rewritten remotely. When coming from an older version, upgrade once with the one-line install script (with --update) or the host's native install command.

Troubleshooting ​

The installer says "already exists; use --force" ​

Normal behavior from the CLI's native installer on an upgrade — just add --force. See the native binary channel above.

Upgraded, but nothing changed ​

Check in order:

  1. The daemon is still the old process — by far the most common. Run yoooclaw daemon restart, then check version in daemon status.
  2. Two CLIs on PATH — run which -a yoooclaw to see whether you're hitting a copy installed from the other channel.
  3. The Hermes gateway didn't restart — you passed --no-restart, or the restart failed. Run hermes gateway restart.
  4. Skills didn't update — if ~/.hermes/skills/yoooclaw/ lacks the .yoooclaw-managed marker (i.e. it was modified by hand), the plugin logs a warning and skips overwriting rather than clobbering your own content.

Self-update appears stuck ​

All state lives under ~/.yoooclaw/hermes-plugin/update/:

FileContents
pending.jsonAn update in flight (from/to versions, originating chat, start time)
update.logThe installer's stdout + stderr
exit_codeThe installer's exit code, written by the detached shell wrapper
result.jsonA terminal record awaiting notification to the user

The tail of update.log plus exit_code tells you exactly where it stopped. A timeout is reported as exit code 124.

Next steps ​