DevClaw Silently Forks Project Registry State by Creating Empty devclaw/projects.json
Workspace scaffolding creates an empty canonical registry without detecting legacy state, causing projects to appear missing after restart.
๐ Symptoms
Observable Behavior
When DevClaw workspace scaffolding encounters a missing canonical registry, it creates an empty one without detecting pre-existing legacy state:
# Canonical registry path
$ cat ~/.openclaw/workspace/devclaw/projects.json
{
"projects": {}
}
The registry is empty despite projects having been registered in prior sessions.
Downstream Manifestations
Users encounter the following operational failures:
- Missing Projects After Restart: Projects registered in previous sessions no longer appear in `devclaw projects list`
- Channel Routing Failures: Task routing fails with
Project not founderrors for previously registered channels - Tool Behavior Anomalies: Project/task tools return empty results or "no projects registered" errors
- Silent State Fork: No warning or error emitted during startup indicating legacy state was bypassed
Diagnostic Command Output
$ devclaw projects list
[]
$ devclaw status
Project Registry: EMPTY
Last Updated: (timestamp of empty file creation)
$ 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
# Legacy registry still exists unexamined
$ ls -la ~/.openclaw/workspace/
total 8
-rw-r--r-- 1 sai sai 2048 Jan 19 14:22 projects.json # Legacy state ignored
drwxr-xr-x 2 sai sai 4096 Jan 20 10:30 devclaw/
๐ง Root Cause
Architectural Context
DevClaw maintains a project registry as its control-plane state. The registry tracks all registered projects, their metadata, and channel routing information. This state is persisted to disk at a canonical path.
Failure Sequence
The critical failure occurs during workspace initialization in the following sequence:
- Workspace Scaffolding Triggered: On first startup or when the canonical directory structure is missing, DevClaw runs workspace scaffolding logic
- Missing Canonical Registry Detected: The scaffolding checks for
~/.openclaw/workspace/devclaw/projects.json - Absence Implies Fresh State: The scaffolding interprets "file missing" as "fresh workspace" without querying legacy locations
- Empty Registry Created: An empty
{ "projects": {} }is written to the canonical path - Legacy State Orphaned: Pre-existing registries at legacy paths are neither detected nor migrated
Code Flow Analysis
// Pseudocode representation of current scaffolding behavior
function initializeWorkspace():
canonicalRegistry = resolvePath("~/.openclaw/workspace/devclaw/projects.json")
if not fileExists(canonicalRegistry):
# BUG: Missing check for legacy registry locations
createDirectoryStructure()
writeEmptyRegistry(canonicalRegistry) # Silent fork occurs here
return
# Only reached if canonical exists
loadRegistry(canonicalRegistry)
Legacy Registry Locations
DevClaw historically supported multiple registry storage paths:
~/.openclaw/workspace/projects.json~/.openclaw/workspace/projects/projects.json~/.openclaw/projects.json
The current scaffolding does not query these paths before creating a fresh canonical registry.
Why This Is Dangerous
The failure is silent because:
- Exit code is 0 (success)
- No console output indicating registry creation
- No integrity check comparing canonical vs. legacy state
- Users receive no indication that historical state exists but was bypassed
๐ ๏ธ Step-by-Step Fix
Phase 1: Preventative Guard (Scaffolding Layer)
The workspace scaffolding logic must check for legacy registries before creating a fresh canonical registry.
Before (vulnerable scaffolding):
function initializeWorkspace():
canonicalRegistry = resolvePath("~/.openclaw/workspace/devclaw/projects.json")
if not fileExists(canonicalRegistry):
createDirectoryStructure()
writeEmptyRegistry(canonicalRegistry)
return
loadRegistry(canonicalRegistry)
After (guarded scaffolding):
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):
// Check for orphaned legacy state
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)
Phase 2: Migration Path
When legacy state is detected, provide a migration command:
CLI Migration Command:
# Detect and display legacy registry locations
$ 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
Migration Execution:
# Migrate from legacy location
$ 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.
Phase 3: Verification of Fix
# Subsequent startup should not create empty registry when legacy exists
$ devclaw start
Error: Legacy registry detected at ~/.openclaw/workspace/projects.json
Run 'devclaw registry migrate' before starting DevClaw.
# After migration, startup proceeds normally
$ devclaw registry migrate --source ~/.openclaw/workspace/projects.json
$ devclaw start
DevClaw initialized successfully.
Projects: 5 registered
Channels: 12 active
๐งช Verification
Test Case 1: Fresh Workspace (No Legacy)
# Clean state: no canonical, no legacy
$ 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":{}}
# Exit code
$ echo $?
0
Test Case 2: Legacy Exists, Canonical Missing
# Setup: legacy exists, no canonical
$ mkdir -p ~/.openclaw/workspace
$ echo '{"projects":{"test-project":{"path":"/home/sai/test"}}}' > ~/.openclaw/workspace/projects.json
$ rm -rf ~/.openclaw/workspace/devclaw
$ devclaw start
# Should FAIL with migration error
Error: Legacy registry detected.
Location: ~/.openclaw/workspace/projects.json
Run: devclaw registry migrate
$ echo $?
1
# Verify empty canonical was NOT created
$ ls ~/.openclaw/workspace/devclaw/
ls: cannot access '~/.openclaw/workspace/devclaw/': No such file or directory
Test Case 3: After Successful Migration
$ 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
Test Case 4: Regression Prevention
# Attempt to bypass migration by pre-creating empty canonical
$ echo '{}' > ~/.openclaw/workspace/devclaw/projects.json
$ devclaw start
# Should detect fork/inconsistency
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
โ ๏ธ Common Pitfalls
Environment-Specific Traps
- Docker Container Initialization: When DevClaw runs inside Docker, volume mounts may create the directory structure but leave
projects.jsonmissing. The container's entrypoint scaffolding will fork state if a legacy exists on the host volume. - macOS Case-Sensitivity: The filesystem is case-insensitive by default.
projects.jsonandProjects.jsonmay both exist in different legacy locations, causing confusing behavior during diagnosis. - Windows Path Resolution: Legacy paths may use backslashes or mixed separators. The guard must normalize paths before comparison.
- Network File Systems (NFS): File existence checks may race with concurrent writes. Use file locking or atomic operations when checking and creating registries.
User Misconfigurations
- Partial Migration: Users may run
devclaw registry migratewithout specifying the correct source path, migrating from an empty legacy location while another populated legacy exists elsewhere. - Manual State Editing: Users editing
projects.jsonmanually may create JSON syntax errors, causing the migration to fail silently and create an empty registry. - Symlink Confusion: Symbolic links to legacy or canonical paths may confuse detection logic. Implement
realpathresolution before comparisons. - Permission Issues: If the user lacks write permissions to create
devclaw/projects.json, the scaffolding may fail without a clear error message, or may create the directory but not the file.
Edge Cases
- Zero-Byte Legacy File: A legacy
projects.jsonthat exists but is empty (0 bytes) should be treated differently from a missing file. The guard should distinguish between "no legacy" and "empty legacy". - Corrupted JSON Legacy: If legacy contains malformed JSON, migration should fail with a specific error rather than falling back to creating an empty canonical registry.
- Concurrent DevClaw Instances: Multiple DevClaw instances starting simultaneously may race to create the empty registry. Use file locking (
flock) during initialization. - Migration Interrupted Mid-Write: If the migration process is killed after copying the legacy but before updating internal state, the system may be left in an inconsistent state. Implement atomic writes or transaction logs.
๐ Related Errors
Logically Connected Error Codes
REGISTRY_NOT_FOUND: Canonical registry path does not exist and no fallback availableREGISTRY_CORRUPTED: Registry file exists but contains invalid JSONLEGACY_REGISTRY_DETECTED: Legacy registry location found during initialization; migration requiredMIGRATION_IN_PROGRESS: Registry migration failed or was interruptedPROJECT_NOT_REGISTERED: Project lookup failed because it is absent from the registryCHANNEL_ROUTING_FAILED: Task routing failed due to missing project metadataWORKSPACE_INIT_FAILED: Scaffolding could not create required directory structure
Historical Issues
- Issue #142: "Project registry intermittently empty after system restart" - Early symptom report that exposed this silent fork behavior
- Issue #156: "Channel routing fails for all projects on fresh session" - Downstream consequence of orphaned empty registry
- Issue #167: "Migration command does not detect all legacy paths" - Incomplete migration path fix
- Issue #189: "Docker volume mounting causes registry state loss" - Environment-specific manifestation of the same root cause
Prevention Checklist
# Pre-flight check before upgrading 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.
# If 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