OpenClaw 2.0 Upgrade on macOS: Diagnosing Hidden Install Issues & Zero-Downtime Migration
A detailed guide to upgrading OpenClaw from 2026.7.1-2 to 2026.8.1 on macOS, covering diagnosis of a misconfigured installation, Node.js version conflicts, choosing the install.sh path for automatic Node 26 provisioning, step-by-step backup and migration, post-install LaunchAgent reconstruction, and troubleshooting ServiceWorker cache causing QClaw branding.
0 A Misdiagnosed Starting Point
Environment: Mac Studio (Apple Silicon, arm64) / macOS 15.7.4. Starting version: OpenClaw 2026.7.1-2, target: 2026.8.1. Total time ~10 minutes, zero data loss.
The requirement seemed simple: evaluate the best way to install OpenClaw 2.0 on macOS. But the first diagnostic step flipped the problem: the machine wasn't "uninstalled" — it was "installed crooked".
$ which openclaw
openclaw not found
$ ls -la ~/.openclaw/
drwx------ agents/ identity/ logs/
-rw------- openclaw.json openclaw.json.bak openclaw.json.last-good
drwxr-xr-x plugins/ service-env/ skill-workshop/
drwx------ skills/ state/ workspace/CLI command not found, yet the state directory is fully populated. Critical evidence from the process table:
$ lsof -nP -iTCP:18789 -sTCP:LISTEN
node 4243 javaedge 23u IPv4 ... TCP 127.0.0.1:18789 (LISTEN)
$ curl -s -o /dev/null -w "%{http_code}
" http://127.0.0.1:18789/
200Gateway has been running normally , just on the old version, while CLI is disconnected due to a PATH issue. This changes the entire technical approach: from "how to fresh install" to "how to switch versions and migrate data without downtime".
1 Diagnosis
1.1 Environment Reality Check
A table of checks shows:
Chip/OS: arm64 / macOS 15.7.4 ✅
Gateway process: PID 4243, listening on 127.0.0.1:18789, HTTP 200 ✅
Running version: OpenClaw 2026.7.1-2 (0790d9f) ❌ one major version behind
CLI availability: which openclaw → not found ❌
Gateway install path: ~/.nvm/versions/node/v20.19.6/lib/node_modules/openclaw ⚠️ bound to EOL Node 20
Gateway runtime: ~/.hermes/node/bin/node = v22.23.1 — only compliant one, but third-party private runtime
Disk free: 94 GiB ✅
LM Studio: listening on *:1234 ✅ auto-discoverable by 2.0
OpenClaw.app: not installed (optional)
1.2 Node: System-Wide Hard Constraint
Official requirement (from docs and installer constant NODE_SUPPORTED_VERSION_LABEL):
Node >=22.22.3 <23 OR >=24.15.0 <25 OR >=25.9.0Every Node on the machine compared:
WorkBuddy managed (first in PATH): v22.22.2 ❌ short by 0.0.1
Homebrew: v22.22.0 ❌
nvm: v24.13.0 ❌ short of 24.15
.hermes/node (private): v22.23.1 ✅ compliant but should not be relied upon
Log proof:
$ tail -30 ~/.openclaw/logs/gateway.err.log
openclaw requires Node >=22.22.3 <23, >=24.15.0 <25, or >=25.9.0.
Detected: node 22.22.0 (exec: /opt/homebrew/Cellar/node@22/22.22.0/bin/node).
PATH searched: /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
Upgrade Node and re-run openclaw.PATH contains 7 AI runtimes fighting for precedence (WorkBuddy / hermes / nvm / homebrew / grok / catpaw / bun). This fact directly dictates solution choice — any "self-managed Node" install path will hit the same wall.
1.3 Anomalies
SQLite State vs Service Version Mismatch
~/.openclaw/state/openclaw.sqlite(4.1 MB) created 8/31 09:16, still written at 9/1 09:46, while resident Gateway version remains 2026.7.1-2. Schema upgraded, service not — continuing to read/write migrated DB with old version is the actual source of state inconsistency.
PATH Pollution
First entry is WorkBuddy's v22.22.2 (non-compliant). Manual openclaw execution would hit the exact error in the log.
Version Trap
Official release notes explicitly mark 2026.9.1-beta.1 as version number incorrect ; actual content is 2026.8.1-beta.4, lower than stable. Do not treat it as "newer than 2.0" .
2 Solution Selection
2.1 Confirm Target Version & Available Assets
curl -s "https://api.github.com/repos/openclaw/openclaw/releases/latest" \
| python3 -c "
import sys,json; d=json.load(sys.stdin)
print(d['tag_name'], d['published_at'], 'prerelease:', d['prerelease'])
[print(' ', a['name'], round(a['size']/1e6,1),'MB') for a in d['assets']]"Output highlights:
v2026.8.1 2026-08-31T03:30:51Z prerelease: False
assets: 40
OpenClaw-2026.8.1.dmg 531.13 MB
OpenClaw-2026.8.1.zip 789.26 MB
OpenClawCompanion-SHA256SUMS.txt
... (many *-verification.json checksum files)Key conclusion: v2026.8.1 indeed ships macOS desktop assets , making GUI path viable.
2.2 Four Official Paths Compared
A. install.sh — curl -fsSL https://openclaw.ai/install.sh | bash. Auto-provisions Node 26; no system Node dependency; official installer bypasses npm freshness filter. ✅ Best fit .
B. macOS Desktop — Download OpenClaw-2026.8.1.dmg (531 MB). Menu bar, ⌥Space Quick Chat, voice wake, TCC permissions, Sparkle auto-update. ✅ Optional, but defer .
C. install-cli.sh — Installs into ~/.openclaw local prefix. Most thorough decoupling from system Node. ⚠️ Adds another entry to already long PATH .
D. npm/pnpm/bun global — npm i -g openclaw@latest --allow-scripts=openclaw. Transparent. ❌ Guaranteed version + PATH double trap .
2.3 Decision: Why A
Single decisive reason — A is the only mainline path that auto-provisions Node 26 , bypassing both "Node all non-compliant" and "PATH chaos" in one shot.
D explicitly excluded: machine Node all non-compliant + 7 runtimes contending, global install makes command resolution and executing Node completely unpredictable.
B not excluded but deferred: desktop app can connect to existing Gateway, no duplicate install needed. Do A first for upgrade + data migration, add GUI later if desired.
Final choice : A (install.sh) as mainline + in-place data retention.
3 Execution: Strict Sequence
3.0 Stop Writes First, Then Backup
Conventional intuition: backup then stop service. Must reverse — Gateway continuously writes SQLite; backing up first copies a DB being modified, backup itself untrustworthy (especially under WAL mode). Correct order: stop writes → backup → install .
3.1 Step 1: Stop Writes & Confirm Port Release
UID_NUM=$(id -u)
launchctl bootout gui/$UID_NUM/ai.openclaw.gateway
sleep 2
pgrep -f "openclaw/dist/index.js" || echo "✅ process gone"
lsof -nP -iTCP:18789 -sTCP:LISTEN || echo "✅ port released"
launchctl list | grep -i claw || echo "✅ removed from launchctl"3.2 Step 2: Backup & Verify
BK=~/openclaw-backup-$(date +%Y%m%d-%H%M%S); mkdir -p "$BK"
cp -a ~/.openclaw "$BK/dot-openclaw"
cp -a ~/Library/LaunchAgents/ai.openclaw.gateway.plist "$BK/"
cp -a ~/Library/Logs/openclaw "$BK/launchd-logs"
# Triple verification
find ~/.openclaw -type f | wc -l # 29
find "$BK/dot-openclaw" -type f | wc -l # 29 → match
python3 -c "import sqlite3;print(sqlite3.connect('$BK/dot-openclaw/state/openclaw.sqlite').execute('PRAGMA integrity_check').fetchone())"
# ('ok',)Backup only 3.2 MB, near-zero cost — but it's the safety baseline: after 2.0 migrates to SQLite, downgrade is irreversible , official docs explicitly require verified backup before upgrade.
3.3 Step 3: Review Script Before Running
curl | bashis official recommendation, but basic review of 4070-line script is necessary:
curl -fsSL https://openclaw.ai/install.sh -o /tmp/openclaw-install.sh
grep -n "sudo" /tmp/openclaw-install.sh | head -5
grep -n "NODE_SUPPORTED_VERSION_LABEL" /tmp/openclaw-install.shReview findings:
sudo only used on Linux for apt-get and macOS group edit prompts, not main install path.
Embedded threshold constant 22.22.3+, 24.15.0+, or 25.9.0+ matches official docs.
Supports --no-onboard non-interactive mode.
Use --no-onboard to avoid interactive wizard hang (wizard requires model selection, login, cannot automate):
cd ~ && curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard3.4 Installer Actual Behavior (Excerpt)
✓ Detected: macos
Install plan: npm / latest / Onboarding: skipped
[1/3] Preparing environment
· Node.js v24.13.0 found, upgrading to a supported version
· Installing Node.js via Homebrew
✓ Active Node.js: v26.8.1 (/opt/homebrew/opt/node/bin/node)
· Active npm: 11.19.0
[2/3] Installing OpenClaw
· Installing OpenClaw v2026.8.1
· Published openclaw bin link at /opt/homebrew/bin/openclaw
[3/3] Finalizing setup
! Multiple OpenClaw global installs detected
- 2026.8.1 /opt/homebrew/lib/node_modules/openclaw
- 2026.7.1-2 /Users/javaedge/.nvm/versions/node/v20.19.6/lib/node_modules/openclaw
· Config already present; running doctor to migrate settings
! Gateway restart failed; try: openclaw daemon restart
🦞 OpenClaw installed successfully (2026.8.1)!Three key signals:
Installer auto-upgraded Node from v24.13.0 to v26.8.1 — official recommended version.
It detected multi-source conflict and warned, but only warns, no auto-cleanup. Gateway restart failed — leads to the most easily missed next step.
4 Cleanup: What Installer Won't Do For You
4.1 LaunchAgent Still Points to Old Package
Post-install status check:
$ openclaw gateway status --deep
Command: /Users/javaedge/.hermes/node/bin/node \
/Users/javaedge/.nvm/versions/node/v20.19.6/lib/node_modules/openclaw/dist/index.js gateway --port 18789
Runtime: stopped (state spawn scheduled)
Service is loaded but not running (likely exited immediately).
Service config issue: Gateway service PATH includes version managers or package managersRoot cause : installer updated npm package but did not rebuild LaunchAgent plist . plist still hardcodes old path pointing to moved package, so it starts and exits immediately.
Fix — must use newly installed CLI to rebuild daemon:
export PATH="/opt/homebrew/bin:$PATH" # ensure new CLI invoked
openclaw gateway uninstall
openclaw gateway installRebuilt plist is correct:
<string>/opt/homebrew/opt/node/bin/node</string>
<string>--max-old-space-size=32768</string>
<string>/opt/homebrew/lib/node_modules/openclaw/dist/index.js</string>
<string>gateway</string>Note --max-old-space-size=32768 — installer auto-configured heap limit based on 128 GB physical RAM, absent in old version.
4.2 Clean Old Install Sources
Official advice: "Keep one install source, then remove stale installs". But:
$ /Users/javaedge/.nvm/versions/node/v20.19.6/bin/npm uninstall -g openclaw
up to date in 81ms # ← ineffective
$ ls -d /Users/javaedge/.nvm/versions/node/v20.19.6/lib/node_modules/openclaw
/Users/javaedge/.nvm/versions/node/v20.19.6/lib/node_modules/openclaw # ← old package still thereReason : package not tracked by npm (possibly manually placed or injected by other tool), npm uninstall has no handle.
Use move instead of delete — same effect, fully reversible:
mkdir -p "$BK/stale-npm-openclaw-2026.7.1-2" # parent must exist first
mv <old-path>/lib/node_modules/openclaw "$BK/stale-npm-openclaw-2026.7.1-2/openclaw"4.3 A Trap Worth Recording: Don't Trust a Single Signal
The mv above returned stderr:
mv: rename ... to .../stale-npm-openclaw-2026.7.1-2/openclaw: No such file or directoryBut immediate check showed source path empty. Two signals contradict.
Handling: verify real result with independent command, not any single output.
ls -d <old-path> 2>/dev/null || echo "cleared"
ls -la "$BK/" # confirm target actually received contentCross-check conclusion: package successfully landed in backup dir, stderr was false alarm. Without cross-check, might misjudge as "old package lost" or "cleanup failed" and take extra actions.
5 Verification: End-to-End Confirmation
$ openclaw --version
OpenClaw 2026.8.1 (ea80657)
$ openclaw gateway status
CLI version: 2026.8.1 (/opt/homebrew/bin/openclaw)
Gateway version: 2026.8.1
Runtime: running (pid 7296)
Connectivity probe: ok
$ curl -s -o /dev/null -w "%{http_code}
" http://127.0.0.1:18789/
200Must confirm CLI version and Gateway version fully match , and plist ProgramArguments points to new package — any one missing leaves "looks installed" illusion.
Read-only health check:
$ openclaw doctor --lint
{"ok":false,"checksRun":30,"checksSkipped":29,"findings":[...2 warnings...]}30 checks pass, only 2 info-level hints (see section 10), none blocking.
Config Migration Confirmed
Compare ~/.openclaw/openclaw.json before/after: meta.lastTouchedVersion: 2026.7.1-2 → 2026.8.1 meta.migrations: none →
modelPolicyAllowlist: true agents.entries: none →
main: {} skills.entries: none → 31 bundled skills (all disabled) wizard.lastRunVersion: — →
2026.8.16 Pitfall Checklist
Service loaded but not running — plist still points to moved old package, installer doesn't rebuild. Fix: gateway uninstall → gateway install.
npm uninstall -g reports up to date, old package remains — package not in npm registry. Fix: mv to backup dir (reversible, better than rm).
mv reports "No such file or directory" but source gone — target parent dir missing, stderr false. Fix: ls -d independent cross-check, don't trust single signal.
devices rotate --role operator → rotation denied — needs interactive re-auth, not automatable. If doctor / devices list normal, no block, leave for terminal.
NODE_TLS_REJECT_UNAUTHORIZED=0 warning — from host execution environment (AI tool injected shell), not OpenClaw setting. Variable appears in openclaw's host-env-security isolation list — it's a controlled object, not setter. User's own terminal unaffected.
Dashboard shows "QClaw" not "OpenClaw" — browser ServiceWorker cache from old derivative fork. See section 9.
Item 5 deserves expansion: initially judged "false positive" but attribution wrong. Cross-check revealed variable exists but source not OpenClaw config:
$ echo "[$NODE_TLS_REJECT_UNAUTHORIZED]"
[0] # ← current shell injected
$ grep -rn "NODE_TLS_REJECT_UNAUTHORIZED" ~/.openclaw/ ~/.zshrc
(no results) # ← not in OpenClaw config, not in user shell configIn openclaw source, variable appears in dist/host-env-security-*.js isolation list — it's a controlled object, not a setter.
7 Data Forensics: Don't Trust File mtime
Mid-diagnosis hypothesized "SQLite DB created by 2.0 migration" based on file timestamps (created 8/31 09:16, written 9/1 09:46). This hypothesis was wrong .
SQLite query reveals truth:
SELECT * FROM schema_meta;
-- meta_key='startup-migrations', app_version='2026.7.1-2', created_at=1787017490221
SELECT * FROM gateway_boot_lifecycle ORDER BY rowid DESC LIMIT 3;
-- pid=4243, started_at_ms=1788138970088, outcome='clean_stop',
-- startup_reason='gateway.crash_loop_recovered'Timestamp conversion:
1787017490221 → 2026-08-18 09:44:50 — SQLite DB created by 2026.7.1-2
1788138970088 → 2026-08-31 09:16:10 — Gateway crash recovery restart
1788273524701 → 2026-09-01 22:38:44 — clean_stop during this upgrade
Corrected conclusion : SQLite DB created by old version on 8/18; 8/31 09:16 is crash restart, not 2.0 migration.
Bonus finding: startup_reason = gateway.crash_loop_recovered proves old version itself unstable — this upgrade's necessity validated.
Methodology : file mtime only reflects last write time, not creation version. Query database metadata for real history, not filesystem timestamps.
8 Launch & First Use
8.1 Gateway Already Running
After upgrade it's already running (PID 7296), and LaunchAgent RunAtLoad=true means auto-start on boot already effective .
Dashboard:
http://127.0.0.1:18789/8.2 Must Add Model Config
Because install used --no-onboard, model integration empty:
$ openclaw models list
Model Input Ctx Local Auth Tags
openai/gpt-5.6-sol - - no no default Auth = no, config lacks models / auth keys. Without this step, Dashboard opens but chat fails.
openclaw onboard # interactive, run in terminal2.0 onboarding scans existing AI access on machine first, not just asking for keys:
Codex / ChatGPT / Claude CLI logins — multiple present
Direct API key — optional
Ollama / LM Studio local models — ✅ LM Studio listening on *:1234, auto-discovered
Local models most valuable for this machine — zero API cost, data never leaves machine . 2.0 behavior: makes a real call to confirm model responds before saving credentials, avoids "configured but doesn't run" surprises.
8.3 Three Entry Points
open http://127.0.0.1:18789/ # browser Dashboard
openclaw agent --message "..." # terminal direct chat
openclaw gateway status # service health check9 Interlude: Why Dashboard Shows "QClaw"?
Dashboard brand shows QClaw not OpenClaw. Serious attribution needed — backend might have been swapped for derivative fork.
9.1 Backend Verification
# ① Package metadata
$ cat /opt/homebrew/lib/node_modules/openclaw/package.json
name: openclaw
version: 2026.8.1
author: OpenClaw Foundation (https://openclaw.org)
homepage: https://github.com/openclaw/openclaw#readme
# ② Search inside package
$ grep -rln "QClaw" /opt/homebrew/lib/node_modules/openclaw/
(0 hits)
# ③ Actual server response
$ curl -s http://127.0.0.1:18789/ | grep -i -c "qclaw"
0
$ curl -s http://127.0.0.1:18789/ | grep -i -o "openclaw" | head -3
openclaw / openclaw / OpenClawServer response contains "QClaw" 0 times, "OpenClaw" normal. Backend is clean.
9.2 Root Cause
Previously installed QClaw (Chinese derivative fork based on OpenClaw), evidence in Application Support residue:
~/Library/Application Support/QClaw/
├── .qclaw/ app-store.json
├── Cache/ Code Cache/
├── Cookies Cookies-journal
└── blob_storage/ /Applications/QClaw.appuninstalled but data dir not cleaned.
QClaw and OpenClaw share port 18789 and similar WebUI resource paths. pgrep -fl QClaw no results — no active process , current 18789 sole listener is official OpenClaw.
Conclusion: Server sends OpenClaw page, but browser's localhost:18789 ServiceWorker/cache still holds QClaw-registered set , new page replaced by old SW showing QClaw brand.
9.3 Fixes (by hit probability)
Hard refresh : Cmd + Shift + R Clear ServiceWorker (most likely): DevTools → Application → Service Workers → for 127.0.0.1:18789 click Unregister ; then Clear storage all → refresh.
Incognito verification : private window visit http://127.0.0.1:18789/ — if shows OpenClaw, confirms pure cache issue.
Nuclear cleanup : chrome://settings/content/all search 127.0.0.1 delete all data.
10 Remaining Items (Non-Blocking)
gateway.auth.tokenplaintext in openclaw.json — low risk (file 600, Gateway loopback-only). Tighten: openclaw secrets configure migrate to SecretRef, then openclaw secrets audit --check verify.
Gateway bound only to loopback — not a defect , official secure default. Change only if remote access needed: gateway.bind=lan.
Local operator device token stale pattern — low (identity dir empty, CLI↔Gateway comms tested ok). openclaw devices rotate needs interactive re-auth, handle when mismatch appears.
Appendix: Complete Command Reference
(Full command list preserved in source — diagnostic, stop, backup, install, cleanup, verify, first-use steps.)
Retrospective
Don't trust "not installed" first impression . which missing ≠ software absent. Check state dir and process table first; problem nature may be completely different.
Installer only does what it promises . It handles package install and Node upgrade, but doesn't rebuild LaunchAgent, doesn't clean old sources, doesn't complete interactive onboarding. These three are exactly the high-frequency "looks installed but unusable" zone.
Attribution must go one step deeper than symptom . This article corrected two judgments: "SQLite created by 2.0 migration" → "created by old version 8/18" (via reading schema_meta not file mtime); NODE_TLS_REJECT_UNAUTHORIZED from "false positive" → "host execution environment injection" (via echo $VAR and source location). Both because first evidence wasn't hard enough.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaEdge
First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
