April 15, 2026

[空白注册表静默分支问题] - 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 维护一个项目注册表作为其控制平面状态。注册表跟踪所有已注册的项目、它们的元数据和通道路由信息。此状态持久化到磁盘上的规范路径。

故障序列

关键故障在工作区初始化期间按以下顺序发生:

  1. 触发工作区脚手架:在首次启动或规范目录结构缺失时,DevClaw 运行工作区脚手架逻辑
  2. 检测到缺失的规范注册表:脚手架检查 ~/.openclaw/workspace/devclaw/projects.json
  3. 缺失意味着全新状态:脚手架将"文件缺失"解释为"全新工作区",而不查询旧位置
  4. 创建空注册表:将空的 { "projects": {} } 写入规范路径
  5. 旧状态被孤立:旧路径上的预先存在的注册表既未被检测也未被迁移

代码流程分析


// 当前脚手架行为的伪代码表示
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.jsonProjects.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:注册表文件存在但包含无效 JSON
  • LEGACY_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

依据与来源

本故障排除指南由 FixClaw 智能管线从社区讨论中自动合成。