1. OpenClaw 启动报 migration failed 与 Schema version mismatch 的真实场景
你升级完 OpenClaw,敲下启动命令,终端直接甩出一行红字:Error: Schema version mismatch Expected: 5, Actual: 3。或者更直接一点,Error: migration failed,后面跟着Column "email" already exists in table "users"。服务起不来,日志里全是迁移相关的报错,这时候你大概会想:我什么都没改,怎么就崩了?
OpenClaw 是一个带本地数据库的 Agent 运行框架,它把会话、项目、审计记录都存在 SQLite 里。每次版本升级,代码里期望的数据库结构(Schema)会变,比如新增一张audit_log表、给users加个索引。这些变更靠迁移文件(migration)来落地。迁移系统维护一张_migrations历史表,记录哪些迁移已经跑过。问题就出在这里:代码升级了,但数据库还停在旧版本,或者迁移跑到一半被中断,历史表和实际表结构对不上,启动时校验直接失败。
这个报错特别容易出现在几种场景:版本升级后忘了执行迁移;多个开发者同时改 Schema,迁移文件顺序乱了;生产环境迁移被 Ctrl+C 打断,数据库卡在 v3 和 v4 之间;迁移文件之间有依赖,004_add_index依赖003_add_column,但 003 没跑成功。我试过在 CI 里自动迁移,结果两个 job 并行跑,直接撞上Migration lock held by another process。
从链路看,OpenClaw 启动时会做三件事:读代码里期望的 Schema 版本号,读数据库_migrations表里的实际版本号,比对。不一致就抛Schema version mismatch。如果版本号一致但迁移文件校验和变了,或者迁移执行时对象已存在,就抛migration failed。所以定位的核心是:先搞清楚数据库现在到底在哪个版本,再决定是补迁移、修历史表,还是从备份恢复。
这一篇不讲虚的,直接给你可复制的 settings 配置片段、迁移前备份命令、版本对齐操作,以及重跑迁移后的验证动作。目标只有一个:让 OpenClaw 正常启动。
2. 把 settings 改到 TaoToken 场景:前置准备与配置片段
在动手修数据库之前,先把 OpenClaw 的模型接入配置理顺。很多迁移失败其实和模型无关,但如果你用的是 TaoToken 这类聚合接入服务,配置写错会导致启动时连模型都初始化不了,报错混在一起更难排查。TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数。
OpenClaw 的配置文件通常在.openclaw/config.json或项目根目录的settings.json。你需要把模型接入部分改成 TaoToken 的 Base URL 和 Key。下面是一个可复制的 JSON 片段,路径和字段名按 OpenClaw 实际结构来:
{ "model": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "modelId": "claude-sonnet-4-20250514", "timeout": 60000, "maxRetries": 3 }, "database": { "path": ".openclaw/data.db", "backupBeforeMigrate": true }, "migration": { "autoMigrate": false, "safeMode": true, "backupBeforeMigrate": true, "transactionPerMigration": true, "stopOnError": true, "lockTimeout": 60000, "retryOnLock": true, "maxRetries": 3, "validateChecksums": true } }这里有几个关键点。baseUrl必须是https://taotoken.net/api,不要写成带/v1的路径,OpenClaw 的 openai-compatible 适配器会自动拼接。modelId填你实际要用的模型 ID,比如 Claude 系列或 GPT 系列,具体以 TaoToken 控制台里显示的为准。apiKey从 TaoToken 控制台的 API Keys 页面生成,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
migration这一段是重点。autoMigrate设为false,避免启动时自动迁移把问题搞得更复杂。safeMode开启后,每个迁移用事务包装,失败自动回滚。backupBeforeMigrate确保迁移前自动备份数据库。validateChecksums会校验迁移文件是否被改动过,防止有人手改已应用的迁移导致版本对不上。
如果你用的是 TOML 格式的配置,比如config.toml,等价写法如下:
[model] provider = "openai-compatible" baseUrl = "https://taotoken.net/api" apiKey = "sk-你的TaoToken密钥" modelId = "claude-sonnet-4-20250514" timeout = 60000 maxRetries = 3 [database] path = ".openclaw/data.db" backupBeforeMigrate = true [migration] autoMigrate = false safeMode = true backupBeforeMigrate = true transactionPerMigration = true stopOnError = true lockTimeout = 60000 retryOnLock = true maxRetries = 3 validateChecksums = true改完配置后,先别急着跑迁移。用openclaw --migration-status看一眼当前状态。如果输出里Current version和Expected version不一致,说明确实需要迁移。如果Pending migrations列表里有多个,注意它们的顺序,依赖关系不能乱。
注意:TaoToken 的 API Key 不要硬编码在会提交到 Git 的文件里。可以用环境变量
TAOTOKEN_API_KEY,然后在配置里写"apiKey": "${TAOTOKEN_API_KEY}",OpenClaw 支持这种占位符替换。
配置改好后,模型接入和数据库迁移是两条独立的链路。模型配置错了,启动时会报连接超时或 401;数据库迁移错了,报的是 Schema 版本不匹配。先把模型配置确认能通,再专心修迁移,排查起来会清晰很多。
3. 可复制配置:迁移前备份、版本对齐与重跑迁移的完整操作
修迁移的核心思路是:先备份,再对齐版本,最后安全重跑。下面每一步都可以直接复制执行。
第一步,迁移前备份。不管数据库现在是什么状态,先复制一份出来。OpenClaw 自带备份命令:
openclaw --backup --before-migration如果命令不可用,直接手动复制 SQLite 文件:
cp .openclaw/data.db .openclaw/data.db.bak.$(date +%Y%m%d_%H%M%S)备份完成后,检查迁移状态和历史:
openclaw --migration-status openclaw --migration-history--migration-status会输出当前版本、期望版本、待执行迁移列表。--migration-history会列出每个迁移文件的执行状态,已应用的打勾,待执行的显示 Pending。如果历史表里显示某个迁移已应用,但实际数据库里对应的表或列不存在,说明迁移被中断了,历史表记录不完整。
第二步,检查数据库实际 Schema。用 Python 直接读 SQLite,确认表和列的真实情况:
import sqlite3 conn = sqlite3.connect('.openclaw/data.db') cursor = conn.cursor() cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = cursor.fetchall() print('表:', [t[0] for t in tables]) try: cursor.execute('SELECT * FROM _migrations ORDER BY version') migrations = cursor.fetchall() print('已应用迁移:') for m in migrations: print(f' {m}') except sqlite3.OperationalError: print('无迁移历史表') cursor.execute('PRAGMA table_info(users)') columns = cursor.fetchall() print('users 表列:') for col in columns: print(f' {col[1]} ({col[2]})') conn.close()如果_migrations表里记录的版本和实际表结构对不上,比如历史表说004_add_index已应用,但users表里没有idx_users_email索引,那就需要修复历史表。可以用openclaw --migration-mark手动标记:
openclaw --migration-mark 004_add_index --applied --force或者反过来,如果实际对象已存在但历史表没记录,标记为已应用跳过执行:
openclaw --migration-mark 004_add_index --applied第三步,版本对齐。如果Current version是 3,Expected version是 5,待执行的是 004 和 005,那就按顺序跑。用安全模式,逐个执行:
openclaw --migrate --safe --only 004_add_index openclaw --migrate --safe --only 005_add_audit_table或者直接迁移到目标版本:
openclaw --migrate --safe --to 5--safe模式会做三件事:事务包装、迁移前自动备份、出错即停。如果 004 失败,005 不会执行,数据库回滚到迁移前状态。
如果迁移文件里有CREATE INDEX或ALTER TABLE ADD COLUMN这种可能重复执行的操作,建议改成幂等写法。比如把CREATE INDEX改成CREATE INDEX IF NOT EXISTS,把ALTER TABLE ADD COLUMN改成先检查列是否存在再添加。下面是一个修复迁移文件的示例:
module.exports = { up: function(db) { const indexes = db.exec( "SELECT name FROM sqlite_master WHERE type='index' AND name='idx_users_email'" ); if (indexes.length === 0) { db.exec('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)'); console.log('索引已创建'); } else { console.log('索引已存在,跳过'); } }, down: function(db) { db.exec('DROP INDEX IF EXISTS idx_users_email'); } };第四步,重跑迁移并验证。迁移执行完后,再次检查状态:
openclaw --migration-status输出里Current version应该等于Expected version,Pending migrations为空。然后启动 OpenClaw:
openclaw如果启动成功,日志里没有 Schema 相关报错,说明迁移链路已经通了。最后做一次数据完整性检查,确认迁移没有丢数据:
import sqlite3 conn = sqlite3.connect('.openclaw/data.db') cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM users') print('users 记录数:', cursor.fetchone()[0]) cursor.execute('SELECT COUNT(*) FROM _migrations') print('迁移记录数:', cursor.fetchone()[0]) conn.close()如果记录数和迁移前备份里的一致,说明数据没丢。到这里,migration failed和Schema version mismatch基本就解决了。
4. 验证请求:确认 OpenClaw 启动成功且模型接入正常
迁移修好后,光看启动日志还不够,得实际发一个请求验证模型接入和数据库读写都正常。OpenClaw 启动后,可以用内置的对话命令或者直接调 API。
先确认服务在跑:
openclaw --status输出里应该显示Running,数据库版本和代码版本一致。然后发一个测试对话:
openclaw chat --message "你好,测试一下连接"如果模型配置用的是 TaoToken,请求会走https://taotoken.net/api。返回正常的话,你会看到模型回复。如果报 401,检查 API Key 是否正确;如果报连接超时,检查 Base URL 是否写成了https://taotoken.net/api,不要多加斜杠或路径。
也可以直接用 curl 测 TaoToken 的接口连通性:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 10 }'返回 JSON 里有choices字段就说明模型接入正常。如果返回401 Unauthorized,去 TaoToken 控制台重新生成 Key,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。如果返回model not found,检查modelId是否和控制台里的一致。
数据库这边,验证迁移后的表结构:
openclaw --migration-status --format json输出 JSON 里pending数组应该为空,current和expected相等。再跑一次openclaw --migration-history,确认所有迁移都是Applied状态。
如果 OpenClaw 有 Web 控制台,打开后检查会话列表、项目列表是否能正常加载。这些数据都从 SQLite 读,能加载说明 Schema 没问题。如果某个页面报no such column或no such table,说明还有迁移没跑完,回到第 3 步继续对齐。
验证通过后,建议把当前数据库再备份一次,标记为“迁移后正常状态”:
cp .openclaw/data.db .openclaw/data.db.after_migration_ok这样下次再出问题,可以直接回滚到这个已知正常的版本。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 报错对照
迁移修完后,启动时可能还会遇到其他报错。下面按真实报错逐条排查。
401 Unauthorized:模型接入的 API Key 无效或过期。检查.openclaw/config.json里的apiKey字段,确认没有多余空格。如果用的是环境变量,确认TAOTOKEN_API_KEY已导出。去 TaoToken 控制台重新生成 Key,地址 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。生成后更新配置,重启 OpenClaw。
local proxy failed:这个报错通常出现在配置了本地代理但代理没启动,或者 Base URL 写成了localhost但端口不对。如果你没有用本地代理,检查配置里是否有proxy字段,删掉或注释。TaoToken 的 Base URL 是https://taotoken.net/api,不需要额外代理。如果公司网络需要走代理,确保代理地址和端口正确,并且代理允许访问taotoken.net。
reading choices:这个报错说明请求发出去了,但返回的 JSON 里没有choices字段。常见原因是模型 ID 写错,或者 API 返回了错误信息但被当成正常响应解析。用 curl 直接测一下,看返回体里是choices还是error。如果是error,根据错误信息调整。比如model not found就换正确的modelId,insufficient quota就去 TaoToken 控制台充值或检查额度。
OAuth 报错:如果 OpenClaw 配置了 OAuth 登录,迁移后可能因为用户表结构变化导致 token 校验失败。检查users表里是否有oauth_token或refresh_token列。如果没有,说明相关迁移没跑。用openclaw --migration-status确认待执行迁移,补跑。如果 OAuth 配置在settings.json里,确认oauth.clientId和oauth.clientSecret没被迁移覆盖。
Migration lock held by another process:迁移锁没释放。先确认没有其他 OpenClaw 进程在跑:
ps aux | grep openclaw如果有,停掉。然后手动清理锁文件(通常在.openclaw/migration.lock):
rm -f .openclaw/migration.lock再重跑迁移。如果用的是 Redis 锁,去 Redis 里删掉对应的 key。
Column already exists:迁移文件重复执行。把迁移文件里的ALTER TABLE ADD COLUMN改成先检查再添加,或者用openclaw --migration-mark标记为已应用跳过。参考第 3 步的幂等写法。
Migration conflict: depends on ... but ... has not been applied:迁移依赖顺序错了。检查迁移文件名前缀,确保被依赖的迁移编号更小。比如004_add_index依赖003_add_column,那 003 必须先跑。如果顺序没问题但 003 没跑成功,先单独跑 003:
openclaw --migrate --safe --only 003_add_column然后再跑 004。
排查时记住一个原则:先看--migration-status和--migration-history,确认版本和状态;再看数据库实际表结构,确认历史表和真实情况是否一致;最后才动迁移文件。不要一上来就--force,那会跳过冲突检查,可能导致数据不一致。
6. 长期稳定:把迁移策略固化到 settings 里
问题解决后,把迁移策略写进配置,避免下次升级再踩坑。核心是三条:生产环境禁止自动迁移、迁移前强制备份、多节点用分布式锁。
生产环境的migration配置:
{ "migration": { "autoMigrate": false, "safeMode": true, "backupBeforeMigrate": true, "transactionPerMigration": true, "stopOnError": true, "allowDrop": false, "requireApproval": true, "lockTimeout": 60000, "retryOnLock": true, "maxRetries": 3, "validateChecksums": true, "immutableApplied": true } }allowDrop设为false,禁止迁移里执行DROP TABLE或DROP COLUMN。requireApproval设为true,迁移前需要人工确认。immutableApplied设为true,已应用的迁移文件不允许修改,改了会被校验和检查拒绝。
多节点环境加分布式锁:
{ "migration": { "clusterLock": { "enabled": true, "backend": "redis", "lockKey": "openclaw:migration:lock", "lockTTL": 600000, "retryCount": 30, "retryDelay": 2000 } } }这样多个节点同时启动时,只有一个能拿到锁执行迁移,其他节点等待。锁 TTL 设 10 分钟,防止死锁。
CI/CD 里的迁移步骤:
steps: - name: Backup database run: openclaw --backup --before-migration - name: Run migrations run: | openclaw --migrate --safe --stop-on-error openclaw --migration-status - name: Verify migrations run: | STATUS=$(openclaw --migration-status --format json) PENDING=$(echo $STATUS | jq '.pending | length') if [ "$PENDING" -gt 0 ]; then echo "有未执行的迁移" exit 1 fi - name: Rollback on failure if: failure() run: openclaw --restore --latest-backup迁移文件纳入 Git 管理,命名用001_、002_前缀保证顺序。已应用的迁移不要改,需要变更就新建一个迁移文件。每次迁移后跑一次openclaw --migration-audit,记录执行时间、执行者、变更内容,方便回溯。
最后,把 TaoToken 的接入配置和迁移配置分开管理。模型接入用环境变量存 Key,迁移配置用settings.json固化策略。这样升级 OpenClaw 时,模型接入不受影响,迁移也有安全网。下次再看到Schema version mismatch,按第 3 步的备份、对齐、重跑流程走一遍,基本十分钟内能恢复。