Skip to main content

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:

StateMeaningWhat happens on wl sync
user_ownedNot managed by WorkloomNever touched — Workloom ignores it
owned_cleanHash matches the last applied versionSafe to update to a new version
owned_missingWorkloom owns this file but it's been deletedRecreated from desired state
owned_locally_modifiedDeveloper edited a generated fileFails — requires explicit resolution
owned_conflictBoth local AND desired state changedFails — requires diff review
Default Behavior: Fail on Conflict

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:

  1. On wl pull — After rendering, Workloom records the SHA-256 hash of each file in .loom/state.json
  2. On wl status or wl sync --check — Workloom recomputes the hash of each tracked file and compares it to the recorded hash
  3. 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 TypeDescriptionExample
CleanFile matches desired stateEverything is in sync
ModifiedDeveloper edited a Workloom-owned fileSomeone changed CLAUDE.md manually
MissingWorkloom-owned file was deleted.claude/skills/my-skill/SKILL.md is gone
UnknownUntracked file in a Workloom-managed pathNew file appeared in .claude/hooks/
Provider mismatchFile was rendered by a different provider than expectedCodex file in a Claude-only target
Policy driftProjection policy changed since last renderWorkspace tightened from generated_local to approval_required
Nested target driftFile in a monorepo target conflicts with parentapps/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:

PolicyBehaviorWhen to use
failStop and report the conflict (default)CI pipelines, cautious environments
keep-localKeep the local version, skip the updateYou intentionally edited the file
restoreOverwrite with the desired stateDiscard local edits, return to clean state
promote-to-overrideSave your edits as a workspace overrideYour 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"
}
]
}
.gitignore Guidance
  • .loom/state.jsongit-ignore this file (local state, not shared)
  • .loom/lock.jsoncommit this file (deterministic installs, like package-lock.json)
  • .loom/binding.tomlcommit 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

CommandWhat it does
wl statusShows workspace info and a summary of drift state
wl diffShows file-level diffs for drifted files
wl sync --checkExits 0 if clean, non-zero if drifted (CI mode)
wl restoreRestores Workloom-owned files to desired state
wl syncApplies 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 drift
  • 1 — Drift detected
  • 4 — Conflict (local modifications)
  • 5 — Policy violation

See the CI/CD Integration guide for complete setup instructions.

Next Steps