git pull. NanoClaw records each sanctioned upgrade in data/upgrade-state.json, and the marker carries three values: the version, the commit, and the tree hash. A startup tripwire compares all three against the running code and refuses to boot when they disagree. A commit that leaves the version alone trips it too. The supported path is the /update-nanoclaw skill.
How an update is staged
An update is a transaction, not a merge in place. The skill drives a controller (scripts/update-nanoclaw.ts) that stages the new code in a separate git worktree, builds and tests it there, and only then touches your live checkout. The controller writes its state to disk after every command, so an interrupted update resumes where it stopped instead of starting over.
By default the transaction lives outside your project, at <parent-of-project>/.nanoclaw-updates/<install-slug>/<id>. The slug defaults to a hash of the project path. NANOCLAW_INSTALL_ID overrides it, so use a distinct ID for each install to keep transaction roots separate. Setting NANOCLAW_UPDATE_DIR replaces that whole path, slug included, so point two installs at one directory and they share a root.
A transaction sits in one of seven phases: conflict, prepared, validated, cutover, complete, rolled-back, and abandoned. Only complete means the update landed.
You see the changes twice before anything stops. The skill shows you the upstream commits, the changed-file buckets, the requirements, and any resolved conflicts after staging. It then shows you the exact changed files, the required migrations, the backup tag, and the rollback command, and asks for one confirmation before it takes your service down.
Updating NanoClaw
Run/update-nanoclaw in a Claude Code session at your project root. The skill archives the controller scripts from the upstream ref into a temporary directory and runs prepare from there, so an update always starts with the upstream controller rather than the one in your checkout. Later commands run from the stage worktree or from your checkout.
1
Prepare
The controller refuses to start unless your working tree is clean and your checkout is on a named branch. It records the current HEAD, creates a backup branch named
backup/pre-update-<hash>-<timestamp>-<unique> and a backup tag named pre-update-<hash>-<timestamp>-<unique>, then creates a stage worktree on branch update-nanoclaw/<id>. Pick one strategy for it to apply there:- Merge, the default.
- Rebase.
- Cherry-pick, with a named commit list.
2
Resolve conflicts, if any
A conflicting merge leaves the transaction in the
conflict phase and the controller exits 2. Resolve the conflict inside the stage worktree, never in your live checkout. Then resume from the stage worktree:3
Validate
Validation runs entirely in the stage worktree. It refreshes your installed skills first, commits the refreshed payloads, then runs
pnpm install --frozen-lockfile, pnpm run build, and pnpm test. If the staged changes touched container/agent-runner, it also runs a Bun install and a tsc --noEmit pass over that directory. If Bun is not on your host, that typecheck is deferred to the image build instead. Your live checkout is still untouched at this point.4
Cut over
Cutover requires a validated transaction, a clean live tree, and a live HEAD that has not moved since prepare. A moved HEAD stops it with
Live checkout moved after the update was staged. The controller then stops the service, drains this install’s containers by label with a 300 second timeout, snapshots your mutable state, resets your checkout to the staged commit, and reinstalls and rebuilds. If the staged changes touched container/, the agent image is refreshed in the same step: bash container/build.sh pull on an install with NANOCLAW_HARDENED_IMAGE=true, and bash container/build.sh otherwise. See the hardened runtime for which of those applies to you.5
Acknowledge the requirements
Your service stays down until every requirement is acknowledged as succeeded. A pending requirement blocks the finish step, and so does one acknowledged as failed. Requirements are derived for you and come in two kinds. A breaking change requirement is raised for each added
[BREAKING] line in the CHANGELOG.md diff. An external component requirement is raised when the onecli-gateway or onecli-cli pin in versions.json moved, because those run outside your checkout and git cannot restore them. Follow docs/onecli-upgrades.md for that one. An external component you acknowledge as succeeded must carry a rollback instruction with it.6
Finish
The finish step stamps the upgrade marker, restarts your service through the detected service mode, and health-verifies the result. A failure at any of those steps rolls your install back automatically.
7
Clean up
Cleanup removes the stage worktree and its branch. It keeps the backup branch, the backup tag, and the snapshot, so you can still roll back afterwards. Pruning takes the id of the transaction to keep, and removes older finished ones and their git refs. Preview it with
--dry-run first. Pruning always retains the keeper, everything newer, anything still in flight, and anything whose state it cannot read safely.There is no
ncl subcommand for upgrading. The admin CLI carries no update resource; the controller scripts are the interface.How the service is restarted
The controller detects how your install runs before it stops or starts anything. It recognizeslaunchd, systemd-user, systemd-system, and nohup for an install started by start-nanoclaw.sh.
A host running NanoClaw outside a supported service wrapper, such as a bare pnpm dev, aborts cutover:
data/ncl.sock exists, and bin/ncl groups list succeeds.
Rolling back
Before cutover the controller copies.env, data, groups, store, start-nanoclaw.sh, and nanoclaw.pid into the transaction’s snapshot/ directory. That snapshot is what restores your databases and group state, so rollback is only available once cutover has created it. Roll back with the controller:
What happens to installed channels
Channel and provider adapters are files copied into your tree from the long-livedchannels and providers branches. They are not part of the main merge, so an upstream merge does not refresh them on its own. Validation refreshes every installed skill inside the stage worktree instead, and a skill that fails to refresh fails the whole validation.
Re-applying is safe and idempotent. It re-fetches the latest adapter files from upstream and overwrites only that skill’s own code. Your credentials in .env, your barrel wiring, and your database state are untouched. The flip side is that it overwrites an adapter file you edited by hand. Commit your edits first so you can recover them, or update that channel yourself.
Updating skills
To refresh skills without a full upstream update, run/update-skills. Its flow:
- Preflight. Your working tree must be clean. The script resolves the registry remote rather than assuming
origin: it tries each configured remote, origin first, and keeps the one that answersgit ls-remote --headsfor the registry branches. SetNANOCLAW_REGISTRY_REMOTE, orNANOCLAW_CHANNELS_REMOTE, to name it yourself. - Detection. The script reads the channel and provider barrels to list which code-carrying skills you have installed. Operational skills copy no code into your tree, so they have nothing to refresh.
- Selection. Refresh everything with
--skills all, or name a subset such as--skills slack,opencode. A name you do not have installed is an error, not a skip. - Re-apply. The refresh is headless.
scripts/update-skills.tsreads the apply directives out of each skill’sSKILL.mdand runs them. A skill that declares no apply directives fails rather than being skipped quietly. - Validation.
pnpm run buildandpnpm test, plus atsc --noEmitpass overcontainer/agent-runnerwhen the refresh changed anything undercontainer/agent-runner/src/. - Container rebuild. If a re-applied skill changed files under
container/, such as a provider’s in-container runtime, the agent image is refreshed. New sessions keep running the old image until that happens, so the refresh is what makes the change live, not the file copy. A channel-adapter-only refresh skips this step.
A provider skill that declares Bun dependencies does not need Bun installed on your host. Without host Bun, the refresh runs the Bun version pinned by
container/Dockerfile.Heavily customized forks
If you have diverged far from upstream, with many local commits or deep changes to core files, merging gets painful./migrate-nanoclaw takes a different approach. It extracts your customizations into a replayable guide, checks out clean upstream in a worktree, and reapplies them on the new base, so there is nothing to merge. It is a sanctioned path and stamps the marker the same way an update does. See the note in Migrate from v1 or OpenClaw for how it differs from the v1 migration path.
When not to update
- Dirty working tree. The controller stops. Commit or stash first. The same applies before
/update-skills. - Detached HEAD. Prepare requires a named branch.
- NanoClaw started by hand. Cutover aborts on an unmanaged process. Stop it first.
- Locally modified adapters or upstream skills. Re-applying a channel skill overwrites its files. Commit your edits so you can recover them.
- Large drift. Prefer
/migrate-nanoclawover a conflict-heavy merge.
After an upgrade
The controller builds, tests, restarts, and health-verifies on its own. Check a few things by hand afterwards:- Container image. If
container/changed, the image was rebuilt or re-pulled. Running agent containers keep the old image until their session ends; new sessions use the new one. - Each channel. Send a test message on every channel you use.
- Logs. Check
logs/nanoclaw.logfor errors.
Troubleshooting
The tripwire stops startup. The banner names the mismatch and prints your code version and commit next to the recorded ones. Which fix applies depends on which of three cases the banner reports.- Git could not identify this checkout. Restore git access, then run
/update-nanoclaw. - Same version, different commit or tree. Your code changed without a matching update record, usually an upstream pull or a local commit. Run
/update-nanoclawfor upstream changes. For a deliberate local commit, follow the local-customization steps in upstreamdocs/upgrade-recovery.md. - No marker, or a version mismatch. A raw
git pullis the usual cause, and so is a marker file that was never written or has become unreadable. Open Claude Code in your project folder and run/update-nanoclaw, which repairs the install and clears the stop.
.git. The checkout’s commit and tree read as unknown, so only the version is compared. Restore git access to get the full check back.
Startup still stops after you fixed the underlying problem. The marker needs stamping by hand. This is the last resort:
package.json.
If something is off, start with Troubleshooting.