Drift Detection
Workloom tracks every rendered file with SHA-256 content hashing and a five-state ownership model. When local files diverge from the desired state — whether by manual edits, accidental deletions, or stale versions — Workloom detects it and tells you exactly what changed.
The Five-State Ownership Model
Every file rendered by Workloom is tracked in .loom/state.json. Each file is in one of five states:
| State | Meaning | What happens on wl sync |
|---|---|---|
user_owned | Not managed by Workloom | Never touched — Workloom ignores it |
owned_clean | Hash matches the last applied version | Safe to update to a new version |
owned_missing | Workloom owns this file but it's been deleted | Recreated from desired state |
owned_locally_modified | Developer edited a generated file | Fails — requires explicit resolution |
owned_conflict | Both local AND desired state changed | Fails — requires diff review |
By default, wl sync will refuse to overwrite a locally modified file. This is intentional — generated files should not silently overwrite your edits. You must explicitly choose a resolution strategy.
How Drift Is Detected
Workloom uses SHA-256 content hashing to track file state:
- On
wl pull— After rendering, Workloom records the SHA-256 hash of each file in.loom/state.json - On
wl statusorwl sync --check— Workloom recomputes the hash of each tracked file and compares it to the recorded hash - Mismatch = drift — If the current hash differs from the recorded hash, the file has drifted
Desired state (from packages)
│
▼
Render plan computes
expected file contents
│
▼
SHA-256 of expected ←── Compare ──→ SHA-256 of actual
file contents file on disk
│ │
└──────── Match? ────────────────────────┘
│ │
Yes: clean No: drifted
Seven Drift Types
Workloom classifies drift into seven distinct categories, each with its own diagnostic message:
| Drift Type | Description | Example |
|---|---|---|
| Clean | File matches desired state | Everything is in sync |
| Modified | Developer edited a Workloom-owned file | Someone changed CLAUDE.md manually |
| Missing | Workloom-owned file was deleted | .claude/skills/my-skill/SKILL.md is gone |
| Unknown | Untracked file in a Workloom-managed path | New file appeared in .claude/hooks/ |
| Provider mismatch | File was rendered by a different provider than expected | Codex file in a Claude-only target |
| Policy drift | Projection policy changed since last render | Workspace tightened from generated_local to approval_required |
| Nested target drift | File in a monorepo target conflicts with parent | apps/web/CLAUDE.md conflicts with root |
Each drift type produces a distinct exit code and diagnostic, making it straightforward to handle in CI pipelines.
Conflict Resolution
When wl sync encounters a locally modified file, you have four options:
| Policy | Behavior | When to use |
|---|---|---|
fail | Stop and report the conflict (default) | CI pipelines, cautious environments |
keep-local | Keep the local version, skip the update | You intentionally edited the file |
restore | Overwrite with the desired state | Discard local edits, return to clean state |
promote-to-override | Save your edits as a workspace override | Your edits should become the new desired state |
Set the default policy in .loom/binding.toml:
[binding]
workspace_id = "wksp_my_project"
conflict_policy = "fail"
Or override per command:
wl sync --on-conflict restore
The State File
.loom/state.json tracks the ownership and hash state of every rendered file:
{
"workspaceId": "wksp_simple_01",
"workspaceRevision": 3,
"lastSyncAt": "2026-05-10T14:30:00Z",
"files": [
{
"path": ".claude/settings.json",
"state": "owned_clean",
"ownerPackage": "@workloom/baseline",
"ownerVersion": "1.0.0",
"installedHash": "sha256:a1b2c3...",
"currentHash": "sha256:a1b2c3...",
"provider": "claude",
"lastAppliedAt": "2026-05-10T14:30:00Z"
},
{
"path": "CLAUDE.md",
"state": "owned_locally_modified",
"ownerPackage": "@workloom/baseline",
"ownerVersion": "1.0.0",
"installedHash": "sha256:d4e5f6...",
"currentHash": "sha256:x7y8z9...",
"provider": "claude",
"lastAppliedAt": "2026-05-09T10:00:00Z"
}
]
}
.loom/state.json— git-ignore this file (local state, not shared).loom/lock.json— commit this file (deterministic installs, likepackage-lock.json).loom/binding.toml— commit this file (workspace binding, shared with team)
The Lock File
.loom/lock.json records the exact resolved versions and content hashes for deterministic installs:
{
"workspaceId": "wksp_simple_01",
"workspaceRevision": 3,
"resolvedAt": "2026-05-10T14:30:00Z",
"rendererVersion": "1.2.3",
"packages": [
{
"scope": "@workloom",
"name": "baseline",
"version": "1.0.0",
"contentHash": "sha256:abc123...",
"manifestHash": "sha256:def456..."
}
],
"renderedFiles": [
{
"path": ".claude/settings.json",
"ownerPackage": "@workloom/baseline",
"ownerVersion": "1.0.0",
"contentHash": "sha256:a1b2c3...",
"provider": "claude"
}
]
}
Using wl sync --locked applies only the locked state — no version resolution, no network calls. This is the CI-safe mode.
CLI Commands for Drift
| Command | What it does |
|---|---|
wl status | Shows workspace info and a summary of drift state |
wl diff | Shows file-level diffs for drifted files |
wl sync --check | Exits 0 if clean, non-zero if drifted (CI mode) |
wl restore | Restores Workloom-owned files to desired state |
wl sync | Applies desired state (respects conflict policy) |
CI Integration
Add drift checking to your CI pipeline:
- name: Check for drift
run: wl sync --check --locked
Exit codes:
0— Clean, no drift1— Drift detected4— Conflict (local modifications)5— Policy violation
See the CI/CD Integration guide for complete setup instructions.
Next Steps
- Learn about Trust & Privacy to understand how Workloom protects sensitive assets
- See CLI Reference: wl sync for full command documentation
- Read the CI/CD Guide for pipeline integration