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 installed | Who upgrades it | How |
|---|---|---|
| Standalone CLI (npm) | You | npm update -g @yoooclaw/cli |
| Standalone CLI (native binary) | You | Re-run install.sh (with --force) / install.ps1 (with -Force) |
| Hermes plugin | The plugin itself (can be triggered from chat) | Ask it to self-update in conversation, or re-run the plugin installer |
| OpenClaw plugin | The host / the plugin itself | openclaw 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
yoooclaw update selfIt only checks, never installs — it reads the npm registry's dist-tags, compares versions, and tells you which command to run. Returned fields:
| Field | Meaning |
|---|---|
dist | Where this binary came from, npm or native. Determined by whether the executable path sits under node_modules |
current / latest | Local version / latest version on the channel |
channel | latest; pass --beta to query the beta dist-tag instead |
updateAvailable | Compares only the x.y.z triple, ignoring prerelease suffixes |
command | The upgrade command when one is available, already matched to dist; null when you're up to date |
yoooclaw update self --beta --format jsonThe npm channel
npm update -g @yoooclaw/cli # stable
npm i -g @yoooclaw/cli@beta # prereleaseThe 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:
# 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 --forceWindows (PowerShell):
& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/cli/install.ps1))) -Version 0.12.0 -ForceUpgrades 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--diragain 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-pathit only installs the binary and leaves your shell config alone — true for both first installs and upgrades.--no-modify-pathis 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:
yoooclaw uninstall # stop the daemon + remove binaries and config, keep data
npm uninstall -g @yoooclaw/cli # required for the npm form, which node_modules ownsuninstall 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):
curl -fsSL https://artifact.yoooclaw.com/cli/install-wuying.sh | sh -s -- --skill claude --forceRefresh 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:
yoooclaw daemon restart
yoooclaw daemon status --format json # confirm `version` changedKnow 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).
Option A: self-update from a conversation (recommended)
The plugin exposes two tools so you can upgrade while chatting with Hermes:
| Tool | What it does |
|---|---|
plugin_update_check | Reads the OSS latest channel marker and compares versions. Results are cached for 5 minutes; force=true bypasses the cache |
plugin_update_apply | Actually 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:
{ "ok": true, "currentVersion": "0.7.0", "latestVersion": "0.7.1",
"channel": "latest", "updateAvailable": true,
"updating": false, "editableInstall": false }The full flow once you confirm:
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:
curl -fsSL https://artifact.yoooclaw.com/hermes-plugin/install.sh | bash -s -- --api-key "ock_..."Windows (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:
| Option | Description |
|---|---|
--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-oss | Skip OSS and fall back to the pip package (pip install yoooclaw-hermes-plugin) |
--no-restart | Don'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
| Code | Trigger | What to do |
|---|---|---|
YOOOCLAW_UPDATE_EDITABLE_INSTALL | The plugin is a pip install -e editable install or a source tree | Self-update would clobber the working tree you're editing, so it refuses outright; upgrade manually in the repo |
YOOOCLAW_UPDATE_IN_PROGRESS | An update is already running | Wait 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_NEWER | The target isn't newer than what's installed | Nothing to do. Self-update never downgrades; use the installer's --version to install an older release |
YOOOCLAW_UPDATE_BAD_VERSION | Malformed version number, or the channel marker returned something invalid | Check the --version spelling; if the channel misbehaves, check whether --oss-base-url points somewhere wrong |
Verify after upgrading
hermes yoooclaw lifecycle statusCheck 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:
openclaw plugins update @yoooclaw/phone-notificationsYou 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 withplugins info --json, then calls the host's native install; install registration,$includeand 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 runopenclaw ntf update --accept-capabilitiesto 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:
- The daemon is still the old process — by far the most common. Run
yoooclaw daemon restart, then checkversionindaemon status. - Two CLIs on PATH — run
which -a yoooclawto see whether you're hitting a copy installed from the other channel. - The Hermes gateway didn't restart — you passed
--no-restart, or the restart failed. Runhermes gateway restart. - Skills didn't update — if
~/.hermes/skills/yoooclaw/lacks the.yoooclaw-managedmarker (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/:
| File | Contents |
|---|---|
pending.json | An update in flight (from/to versions, originating chat, start time) |
update.log | The installer's stdout + stderr |
exit_code | The installer's exit code, written by the detached shell wrapper |
result.json | A 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
- Standalone CLI: Overview & Installation — full install instructions for both distribution channels.
- Hermes Plugin: Overview & Installation — what the installer actually does.
- Standalone CLI: Debugging — the three-step check when things won't connect after an upgrade.