笔记 by 车东

OpenClaw 2026.9.x 升级连环坑:no such column workspace_dir / state-migrated-no-rollback / LaunchAgent environment wrapper needs validation 修复

· 运维

openclaw update 升 2026.9.3 时 openclaw doctor 卡在状态库迁移:SQLite canonical index idx_skill_workshop_collection_reviews_workspace_time failed ... no such column: workspace_dir。更新恢复机制按设计保持 Gateway 停止,于是 Telegram / Feishu 全部无响应。根因是 2026.9.x 迁移代码只补了 claim_released_time 列、漏补 workspace_dir 列;把两张空的 skill_workshop 表删掉让 canonical DDL 重建即可。

TLDR

  1. 现象:升级 2026.9.3 失败,Gateway 起不来,通道无响应。日志(triage handoff)显示 reason: "runtime-verification-failed",doctor 停在 no such column: workspace_dir。
  2. 根因(上游迁移 bug):2026.9.x 把 skill-workshop 的 skill_workshop_collection_reviews 表从 owner_agent_id 列改成 workspace_dir 列(proposals 表新增 workspace_dir NOT NULL + claim_released_time),但迁移代码 ensureSkillWorkshopSchema 只执行了 ensureColumn(..., "claim_released_time INTEGER"),没有补 workspace_dir。随后 repairCanonicalSqliteIndexes 重建 canonical 索引 idx_..._workspace_time ON (workspace_dir, ...) 时列不存在 → 抛错 → doctor 拒绝继续 → 更新恢复保持 Gateway 停止。
  3. 修复:4 张 skill_workshop_* 表全部是空的,直接删表 + 删旧索引,让 canonical DDL(CREATE TABLE IF NOT EXISTS + 正确索引)按需重建。备份后零数据损失。
  4. 验证:openclaw doctor EXIT 0 → openclaw gateway start → Telegram / Feishu running, connected → 重跑 openclaw update 顺利升到 2026.9.3。

现象

openclaw update 输出里 doctor 阶段失败,诊断手稿(~/.openclaw/logs/support/openclaw-triage-prompt-*.md)记录:

{"result":{"status":"error","mode":"npm","reason":"openclaw doctor",
  "before":{"version":"2026.9.2"},"after":{"version":"2026.9.2"},
  "recovery":{"serviceRestartSafe":false,"reason":"runtime-verification-failed",...},
  "steps":[{"name":"openclaw doctor","exitCode":1,...}]}}

doctor 的具体报错:

Failed migrating shared state database schema at /Users/chedong/.openclaw/state/openclaw.sqlite:
SQLite canonical index idx_skill_workshop_collection_reviews_workspace_time
failed for /Users/chedong/.openclaw/state/openclaw.sqlite: no such column: workspace_dir

openclaw status 只有 run error,通道 disconnected。

根因:workspace_dir 列在迁移路径里没人补

2026.9.x 的 skill-workshop canonical schema 把按「owner agent」组织的表改成了按「workspace 目录」组织:

表 旧结构(2026.9.2 DB 实际) canonical(2026.9.3)
skill_workshop_collection_reviews 列 owner_agent_id NOT NULL;索引 idx_..._owner_time(owner_agent_id, create_time DESC, review_id) 列 workspace_dir NOT NULL;索引 idx_..._workspace_time(workspace_dir, create_time DESC, review_id DESC)
skill_workshop_proposals 无 workspace_dir、无 claim_released_time 新增 workspace_dir TEXT NOT NULL + claim_released_time INTEGER

但迁移代码(dist/store-sqlite-record-*.js 的 ensureSkillWorkshopSchema)只做了:

db.exec(SCHEMA_SQL);
ensureColumn(db, "skill_workshop_proposals", "claim_released_time INTEGER");

db.exec(SCHEMA_SQL) 里是 CREATE TABLE IF NOT EXISTS,对已存在的表不会改列;ensureColumn 只补了 claim_released_time,漏了 workspace_dir。于是状态库启动迁移(dist/openclaw-state-db-*.js 的 ensureSchema)在 repairCanonicalSqliteIndexes 重建 canonical 索引时,对不存在的 workspace_dir 列建索引 → no such column。迁移拒绝继续,doctor EXIT 1,更新恢复按设计保持 Gateway 停止。

顺带一提:这个失败路径在日志里几乎看不到,~/Library/LaunchAgents/ai.openclaw.gateway.plist 把 StandardErrorPath 指到 /dev/null,底层错误被吞掉,只看到「Gateway 没响应」。

修复:删掉空的 skill_workshop 表让 canonical 重建

先确认两张关键表都是空表(无数据,删了零损失):

-- 行数全部为 0
SELECT count(*) FROM skill_workshop_collection_reviews;  -- 0
SELECT count(*) FROM skill_workshop_proposals;           -- 0
SELECT count(*) FROM skill_workshop_proposal_events;     -- 0
SELECT count(*) FROM skill_workshop_proposal_rollbacks;  -- 0

备份后删表 + 删旧索引(注意 FK 依赖:先删 proposal_events / proposal_rollbacks,再删 proposals):

-- 先备份:cp ~/.openclaw/state/openclaw.sqlite ~/.openclaw/backups/
DROP INDEX IF EXISTS idx_skill_workshop_collection_reviews_owner_time;
DROP TABLE IF EXISTS skill_workshop_proposal_events;
DROP TABLE IF EXISTS skill_workshop_proposal_rollbacks;
DROP TABLE IF EXISTS skill_workshop_collection_reviews;
DROP TABLE IF EXISTS skill_workshop_proposals;

这些表属于 lazy additive tables,doctor / gateway 启动时不会立刻重建,但首次访问 skill-workshop store 时 ensureSkillWorkshopSchema 会用 canonical DDL(含 workspace_dir 列和正确索引)自动重建。所以删完直接跑:

openclaw doctor        # EXIT 0,迁移通过
openclaw gateway start # LaunchAgent 重新 bootstrap
openclaw channels status  # Telegram / Feishu: running, connected

修复后重跑 openclaw update,这次一路绿灯:

activating — openclaw doctor...   ✓ (27.21s)
✅ OpenClaw updated to 2026.9.3 (from 2026.9.2).
Gateway: restarted and verified.

验证

  • openclaw --version → OpenClaw 2026.9.3 (1391f7c)
  • openclaw status → Gateway reachable,LaunchAgent loaded · running
  • openclaw channels status → Feishu / Telegram 均 running, connected

后续:2026.9.5 / 2026.9.6 两次升级又卡 Gateway(2026-09-20 / 2026-09-27 补记)

上次修好 2026.9.3 后,后续两次升级(2026.9.5、2026.9.6)又在 openclaw update 时把 Gateway 卡停。根因和上次不同——这次不是表结构漂移,而是插件 deferred 迁移死锁 + 新版服务机制校验。全程仍无需手工改 DB,openclaw 自带命令即可解。

TLDR

  1. 2026.9.5(错误码 state-migrated-no-rollback):升级后状态库里 migration_runs 有两条 deferred-plugin-migration:feishu / :opencode 记录 pending,报 Plugin "feishu"/"opencode" state migration is pending: Package convergence must wait until the updating parent releases its install records.,阻塞 doctor;而 openclaw update repair 又因主 agent 的 legacy sessions.json 已迁入 SQLite、拿不到「verified source index」而报 Deferred plugin session import has no verified source index —— 两环互锁。解法:跑普通 openclaw doctor 让 inspection 自动 resolve 两条 pending(日志 Deferred state migration completed for plugin ...),再 openclaw doctor --fix 清理 legacy session 残留,然后 openclaw update 收尾。
  2. 2026.9.6(Gateway not loaded):升级后服务直接不在运行,两个新原因:① 官方插件版本漂移 —— feishu/opencode 停在 2026.9.5,Plugin version drift: 2 active official plugins not on gateway 2026.9.6,again 触发两条 deferred pending;② LaunchAgent environment wrapper 校验失败 —— 2026.9.6 新增 wrapper 机制,旧 plist 不通过校验,Service config issue: LaunchAgent environment wrapper needs validation; reinstall the Gateway service.。解法:openclaw plugins update feishu opencode 升级插件(自动 resolve pending)→ openclaw doctor --fix → openclaw gateway install --force 重装服务 → openclaw gateway restart。

2026.9.5:state-migrated-no-rollback 两环死锁

openclaw update 输出:

openclaw doctor 失败(error state-migrated-no-rollback)
Plugin "feishu" state migration is pending: Package convergence must wait until the updating parent releases its install records. ...
Plugin "opencode" state migration is pending: ...

此时 migration_runs 里可以看到两条 pending 记录(~/.openclaw/state/openclaw.sqlite):

SELECT id, status, report_json FROM migration_runs
WHERE id LIKE 'deferred-plugin-migration:%';
-- 两条 status = 'pending'

这代表 feishu / opencode 两个已装插件包(openclaw-feishu-*、openclaw-opencode-provider-*)的 state migration 被推迟了:插件包版本与主包还没收敛,系统先把它们的迁移输入保留住,等 owner(doctor inspection)来 resolve。而 update recovery 想用 openclaw update repair 验证可运行安装时,需要为 deferred 插件重建 verified source index;但主 agent 的 legacy sessions.json 早已迁入 SQLite,readMigrationArtifactIdentity 拿不到原始文件,于是:

Deferred plugin session import has no verified source index.

两环互锁,update 无法证明安装可运行 → 按设计保持 Gateway 停止。

解法(全部是官方命令,无需动 DB):

openclaw doctor            # inspection 自动 resolve 两条 pending
#   [state-migrations] Deferred state migration completed for plugin "feishu".
#   [state-migrations] Deferred state migration completed for plugin "opencode".
openclaw doctor --fix      # canonicalize 2 行 legacy delivery 字段,legacy→SQLite
openclaw update            # ✅ OpenClaw updated to 2026.9.5 (from 2026.9.5)
openclaw gateway restart   # 原子重启(LaunchAgent)
openclaw health --json     # ok: true

2026.9.6:插件版本漂移 + LaunchAgent environment wrapper

openclaw update 到 2026.9.6 后 Gateway 仍 not loaded。openclaw gateway status --deep 给出两个新信号:

Service config issue: LaunchAgent environment wrapper needs validation; reinstall the Gateway service.
Plugin version drift: 2 active official plugins not on gateway 2026.9.6
Fix: update each drifted plugin:
- openclaw plugins update feishu
- openclaw plugins update opencode

原因一:官方插件版本漂移。 feishu / opencode 停在 2026.9.5,主包已 2026.9.6,于是又生成了两条 deferred-plugin-migration pending(和 2026.9.5 同款死锁又触发一次)。先把插件升上来:

openclaw plugins update feishu opencode
# Updated feishu: 2026.9.5 -> 2026.9.6.
# Updated opencode: 2026.9.5 -> 2026.9.6.
# [state-migrations] Deferred state migration completed for plugin "feishu"/"opencode".

原因二:2026.9.6 新增 LaunchAgent environment wrapper。 新版要求 plist 带 environment wrapper,旧 plist 不通过校验 → 服务不被加载。需要重装服务:

openclaw doctor --fix            # 顺带自动迁移 agent DB schema v21 -> v23
openclaw gateway install --force # 重装 LaunchAgent(含新版 env wrapper)
#   Installed LaunchAgent: /Users/chedong/Library/LaunchAgents/ai.openclaw.gateway.plist
#   注意:日志位置从 /tmp/openclaw/ 改为 ~/Library/Logs/openclaw/gateway.log
openclaw gateway restart
openclaw health --json            # ok: true
openclaw channels status          # Feishu / Telegram: running, connected

经验:2026.9.x 系列每次 openclaw update 后都要看三点 —— ① migration_runs 有无新的 deferred-plugin-migration pending;② 有无 Plugin version drift(插件落后于主包);③ Gateway 是否 not loaded(新版本服务机制校验旧 plist 失败)。三者都可用 openclaw doctor / plugins update / gateway install --force 解,别手工删 migration_runs 行(那正是系统在保护迁移输入)。

命令速查

# 状态与日志
openclaw status
openclaw channels status
openclaw doctor          # 迁移卡住时的诊断入口
openclaw update cleanup --dry-run   # 更新成功后清理回滚包

# 定位 skill-workshop 表结构漂移
sqlite3 ~/.openclaw/state/openclaw.sqlite ".schema skill_workshop_collection_reviews"
sqlite3 ~/.openclaw/state/openclaw.sqlite "PRAGMA user_version;"   # 15

# 修列缺失:删空表让 canonical DDL 重建(先备份,确认表全空)
# DROP INDEX idx_..._owner_time; DROP TABLE skill_workshop_*;

# 2026.9.5+:升级后 Gateway 卡停的通用排查(先看这三处,再决定命令)
sqlite3 ~/.openclaw/state/openclaw.sqlite \
  "SELECT id,status FROM migration_runs WHERE id LIKE 'deferred-plugin-migration:%';"  # pending?
openclaw gateway status --deep    # Plugin version drift? Service config issue?
openclaw doctor                    # 自动 resolve deferred-plugin-migration pending
openclaw doctor --fix              # 清理 legacy session 残留 + agent DB schema 自动迁移
openclaw plugins update feishu opencode   # 官方插件落后于主包时先升插件
openclaw gateway install --force   # 2026.9.6+ environment wrapper 校验失败时重装服务
openclaw gateway restart           # 原子重启(绝不 stop-then-start)
openclaw health --json             # ok: true 才算起来

注意:动状态库前先备份 ~/.openclaw/state/openclaw.sqlite;只删确认全空的表,有数据的表要先迁移/回填 workspace_dir。2026.9.x 升级后的 deferred-plugin-migration pending 记录是系统在保护迁移输入,不要手工删行——用 openclaw doctor / openclaw plugins update 让系统自行 resolve。