[空白注册表静默分支问题] - DevClaw Silently Forks Project Registry State by Creating Empty devclaw/projects.json
工作区脚手架在创建空的规范注册表时未检测遗留状态,导致项目在重启后显示为缺失。
🔍 症状
可观察行为
当 DevClaw 工作区脚手架遇到缺失的规范注册表时,它会创建一个空注册表而不检测预先存在的旧状态:
# 规范注册表路径
$ cat ~/.openclaw/workspace/devclaw/projects.json
{
"projects": {}
}
尽管项目已在之前的会话中注册,注册表却是空的。
下游表现
用户会遇到以下操作失败:
- 重启后项目消失:在之前的会话中注册的项目不再出现在 `devclaw projects list` 中
- 通道路由失败:任务路由对之前注册的通道失败,并显示
Project not found错误 - 工具行为异常:项目/任务工具返回空结果或"未注册项目"错误
- 静默状态分支:启动时没有发出警告或错误,表明旧状态被绕过
诊断命令输出
$ devclaw projects list
[]
$ devclaw status
Project Registry: EMPTY
Last Updated: (空文件创建的时间戳)
$ ls -la ~/.openclaw/workspace/devclaw/
total 8
drwxr-xr-x 2 sai sai 4096 Jan 20 10:30 devclaw/
-rw-r--r-- 1 sai sai 0 Jan 20 10:30 projects.json
# 旧注册表仍然存在但未被检查
$ ls -la ~/.openclaw/workspace/
total 8
-rw-r--r-- 1 sai sai 2048 Jan 19 14:22 projects.json # 旧状态被忽略
drwxr-xr-x 2 sai sai 4096 Jan 20 10:30 devclaw/
🧠 根因分析
架构背景
DevClaw 维护一个项目注册表作为其控制平面状态。注册表跟踪所有已注册的项目、它们的元数据和通道路由信息。此状态持久化到磁盘上的规范路径。
故障序列
关键故障在工作区初始化期间按以下顺序发生:
- 触发工作区脚手架:在首次启动或规范目录结构缺失时,DevClaw 运行工作区脚手架逻辑
- 检测到缺失的规范注册表:脚手架检查
~/.openclaw/workspace/devclaw/projects.json - 缺失意味着全新状态:脚手架将"文件缺失"解释为"全新工作区",而不查询旧位置
- 创建空注册表:将空的
{ "projects": {} }写入规范路径 - 旧状态被孤立:旧路径上的预先存在的注册表既未被检测也未被迁移
代码流程分析
// 当前脚手架行为的伪代码表示
function initializeWorkspace():
canonicalRegistry = resolvePath("~/.openclaw/workspace/devclaw/projects.json")
if not fileExists(canonicalRegistry):
# BUG: 缺少对旧注册表位置的检查
createDirectoryStructure()
writeEmptyRegistry(canonicalRegistry) # 静默分支在此发生
return
# 仅在规范存在时到达此处
loadRegistry(canonicalRegistry)
旧注册表位置
DevClaw 历史上支持多个注册表存储路径:
~/.openclaw/workspace/projects.json~/.openclaw/workspace/projects/projects.json~/.openclaw/projects.json
当前脚手架在创建新的规范注册表之前不会查询这些路径。
为什么这是危险的
故障是静默的,因为:
- 退出代码为 0(成功)
- 没有控制台输出指示注册表创建
- 没有比较规范状态与旧状态的完整性检查
- 用户没有收到任何指示表明历史状态存在但被绕过
🛠️ 逐步修复
阶段 1:预防性保护(脚手架层)
工作区脚手架逻辑必须在创建新的规范注册表之前检查旧注册表。
修复前(有漏洞的脚手架):
function initializeWorkspace():
canonicalRegistry = resolvePath("~/.openclaw/workspace/devclaw/projects.json")
if not fileExists(canonicalRegistry):
createDirectoryStructure()
writeEmptyRegistry(canonicalRegistry)
return
loadRegistry(canonicalRegistry)
修复后(有保护的脚手架):
function initializeWorkspace():
canonicalRegistry = resolvePath("~/.openclaw/workspace/devclaw/projects.json")
legacyRegistries = [
resolvePath("~/.openclaw/workspace/projects.json"),
resolvePath("~/.openclaw/workspace/projects/projects.json"),
resolvePath("~/.openclaw/projects.json")
]
if not fileExists(canonicalRegistry):
// 检查孤立的旧状态
existingLegacy = findFirstExisting(legacyRegistries)
if existingLegacy is not null:
throw MigrationRequiredError(
"Legacy registry found at: " + existingLegacy.path +
". Migrate before initializing fresh workspace."
)
createDirectoryStructure()
writeEmptyRegistry(canonicalRegistry)
return
loadRegistry(canonicalRegistry)
阶段 2:迁移路径
当检测到旧状态时,提供迁移命令:
CLI 迁移命令:
# 检测并显示旧注册表位置
$ devclaw registry diagnose
Registry Diagnostic Report
===========================
Canonical Path: ~/.openclaw/workspace/devclaw/projects.json
Status: MISSING
Legacy Registries Found:
- ~/.openclaw/workspace/projects.json (MODIFIED: 2024-01-19 14:22)
- ~/.openclaw/projects.json (MODIFIED: 2023-12-15 09:30)
To migrate legacy state:
$ devclaw registry migrate --source ~/.openclaw/workspace/projects.json
To start fresh (WARNING: deletes legacy state):
$ devclaw registry reset --force
迁移执行:
# 从旧位置迁移
$ devclaw registry migrate --source ~/.openclaw/workspace/projects.json
Migrating registry...
Source: ~/.openclaw/workspace/projects.json
Target: ~/.openclaw/workspace/devclaw/projects.json
Projects to migrate: 5
Channels to migrate: 12
[████████████████████] 100%
Migration complete. 5 projects migrated successfully.
阶段 3:修复验证
# 后续启动时当旧状态存在时不应创建空注册表
$ devclaw start
Error: Legacy registry detected at ~/.openclaw/workspace/projects.json
Run 'devclaw registry migrate' before starting DevClaw.
# 迁移后,启动正常进行
$ devclaw registry migrate --source ~/.openclaw/workspace/projects.json
$ devclaw start
DevClaw initialized successfully.
Projects: 5 registered
Channels: 12 active
🧪 验证
测试用例 1:全新工作区(无旧状态)
# 清洁状态:无规范、无旧状态
$ rm -rf ~/.openclaw/workspace/devclaw
$ rm -f ~/.openclaw/workspace/projects.json
$ devclaw start
DevClaw initialized successfully.
Canonical registry created: ~/.openclaw/workspace/devclaw/projects.json
$ cat ~/.openclaw/workspace/devclaw/projects.json
{"projects":{}}
# 退出代码
$ echo $?
0
测试用例 2:旧状态存在,规范缺失
# 设置:旧状态存在,无规范
$ mkdir -p ~/.openclaw/workspace
$ echo '{"projects":{"test-project":{"path":"/home/sai/test"}}}' > ~/.openclaw/workspace/projects.json
$ rm -rf ~/.openclaw/workspace/devclaw
$ devclaw start
# 应失败并显示迁移错误
Error: Legacy registry detected.
Location: ~/.openclaw/workspace/projects.json
Run: devclaw registry migrate
$ echo $?
1
# 验证空规范未被创建
$ ls ~/.openclaw/workspace/devclaw/
ls: cannot access '~/.openclaw/workspace/devclaw/': No such file or directory
测试用例 3:成功迁移后
$ devclaw registry migrate --source ~/.openclaw/workspace/projects.json
Migration complete.
$ devclaw start
DevClaw initialized successfully.
$ cat ~/.openclaw/workspace/devclaw/projects.json
{"projects":{"test-project":{"path":"/home/sai/test"}}}
$ devclaw projects list
test-project
测试用例 4:回归预防
# 尝试通过预创建空规范来绕过迁移
$ echo '{}' > ~/.openclaw/workspace/devclaw/projects.json
$ devclaw start
# 应检测到分支/不一致
Warning: Canonical registry is empty but legacy state exists.
Canonical: ~/.openclaw/workspace/devclaw/projects.json
Legacy: ~/.openclaw/workspace/projects.json (2048 bytes)
Run 'devclaw registry migrate' to reconcile.
$ echo $?
1
⚠️ 常见陷阱
环境特定陷阱
- Docker 容器初始化:当 DevClaw 在 Docker 内运行时,卷挂载可能创建目录结构但让
projects.json缺失。如果主机卷上存在旧状态,容器的入口点脚手架将导致状态分支。 - macOS 大小写敏感性:文件系统默认不区分大小写。不同旧位置可能同时存在
projects.json和Projects.json,导致诊断期间行为混乱。 - Windows 路径解析:旧路径可能使用反斜杠或混合分隔符。保护必须在比较前规范化路径。
- 网络文件系统(NFS):文件存在检查可能与并发写入竞争。在检查和创建注册表时使用文件锁或原子操作。
用户错误配置
- 部分迁移:用户可能运行
devclaw registry migrate时未指定正确的源路径,从一个空的旧位置迁移,而另一个填充的旧位置存在于其他位置。 - 手动状态编辑:用户手动编辑
projects.json可能创建 JSON 语法错误,导致迁移静默失败并创建空注册表。 - 符号链接混淆:到旧位置或规范路径的符号链接可能混淆检测逻辑。在比较前实现
realpath解析。 - 权限问题:如果用户缺乏创建
devclaw/projects.json的写权限,脚手架可能失败而没有清晰的错误消息,或者可能创建目录但不创建文件。
边缘情况
- 零字节旧文件:存在的旧
projects.json但为空(0 字节)应与缺失文件区别对待。保护应区分"无旧状态"和"旧状态为空"。 - 损坏的 JSON 旧文件:如果旧文件包含格式错误的 JSON,迁移应失败并显示特定错误,而不是回退到创建空规范注册表。
- 并发 DevClaw 实例:多个 DevClaw 实例同时启动可能竞争创建空注册表。在初始化期间使用文件锁(
flock)。 - 迁移写入中断:如果迁移过程在复制旧文件后但在更新内部状态前被杀死,系统可能处于不一致状态。实现原子写入或事务日志。
🔗 相关错误
逻辑关联的错误代码
REGISTRY_NOT_FOUND:规范注册表路径不存在且没有备用方案REGISTRY_CORRUPTED:注册表文件存在但包含无效 JSONLEGACY_REGISTRY_DETECTED:初始化期间发现旧注册表位置;需要迁移MIGRATION_IN_PROGRESS:注册表迁移失败或被中断PROJECT_NOT_REGISTERED:项目查找失败,因为它不在注册表中CHANNEL_ROUTING_FAILED:由于缺少项目元数据导致任务路由失败WORKSPACE_INIT_FAILED:脚手架无法创建所需目录结构
历史问题
- Issue #142:"系统重启后项目注册表间歇性为空" - 暴露此静默分支行为的早期症状报告
- Issue #156:"全新会话中所有项目的通道路由失败" - 孤立空注册表的下游后果
- Issue #167:"迁移命令未检测所有旧路径" - 不完整的迁移路径修复
- Issue #189:"Docker 卷挂载导致注册表状态丢失" - 相同根因的环境特定表现
预防检查清单
# 升级 DevClaw 前的预检
$ devclaw registry pre-flight-check
Running pre-flight checks...
[✓] Verifying canonical registry path accessibility
[✓] Checking for legacy registry locations
[✓] Validating registry JSON integrity
[✓] Confirming write permissions
Pre-flight complete. No issues detected.
# 如果检测到问题:
$ devclaw registry pre-flight-check --verbose
Running pre-flight checks...
[✓] Canonical: ~/.openclaw/workspace/devclaw/projects.json (exists)
[⚠] Legacy detected: ~/.openclaw/workspace/projects.json (2048 bytes)
[!] Action required: Run 'devclaw registry migrate' before upgrading