Skip to main content

CI/CD Integration

Use Workloom in your CI/CD pipeline to detect and prevent AI configuration drift. The wl sync --check command is designed for CI—it exits with status codes that integrate cleanly with build systems.

Quick Start: Drift Detection in CI

Add this one command to your CI pipeline to check for drift:

wl sync --check
  • Exit code 0 — All files in sync, build passes
  • Exit code 4 — Drift detected, build fails
  • Exit code 2 — Auth error (workspace binding not found)
  • Exit code 3 — Network error
  • Exit code 5 — Policy violation (trust gate failure)

Exit Code Reference

CodeMeaningAction
0Clean — workspace matches desired stateBuild passes
1Validation failed — manifest errorCheck workspace manifest
2Auth error — binding or credentials missingConfigure auth for CI
3Network error — registry unreachableCheck network connectivity
4Drift detected — files differ from desired stateRun wl status locally
5Policy violation — trust gate not passedReview and approve assets

The wl sync --check Command

wl sync --check

Does not write anything to disk. Instead:

  1. Computes the desired state from your workspace manifest
  2. Compares it to what's currently on disk
  3. Reports drift if found
  4. Exits with appropriate code

Locked Versions Only

For deterministic CI, check against locked versions only:

wl sync --check --locked

This uses the lock file (generated by wl pull) and rejects any manifest changes. Useful in CI where you want to enforce reproducibility.

Dry-Run Mode

Preview what would be applied without checking:

wl sync --dry-run
# Shows the render plan (desired state)
# Does not compare to current state
# Does not write anything

GitHub Actions Example

name: Check AI Configuration Drift

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

jobs:
workloom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 9

- name: Install dependencies
run: pnpm install

- name: Build Workloom
run: pnpm build

- name: Check AI configuration drift
run: pnpm wl sync --check --locked
env:
WORKLOOM_REGISTRY_URL: ${{ secrets.WORKLOOM_REGISTRY_URL }}
WORKLOOM_SERVICE_ACCOUNT_TOKEN: ${{ secrets.WORKLOOM_SERVICE_ACCOUNT_TOKEN }}

The env variables are only needed in Phase 2 when Workloom has a hosted registry. In Phase 1, wl sync --check works locally without auth.

Azure DevOps Pipeline Example

trigger:
- main
- develop

pr:
- main

pool:
vmImage: 'ubuntu-latest'

steps:
- task: NodeTool@0
inputs:
versionSpec: '20'

- task: Bash@3
inputs:
targetType: 'inline'
script: |
npm install -g pnpm@9
pnpm install

- task: Bash@3
inputs:
targetType: 'inline'
script: |
pnpm build
pnpm wl sync --check --locked
env:
WORKLOOM_REGISTRY_URL: $(WORKLOOM_REGISTRY_URL)
WORKLOOM_SERVICE_ACCOUNT_TOKEN: $(WORKLOOM_SERVICE_ACCOUNT_TOKEN)
displayName: 'Check AI configuration drift'

Workspace Policies

Control CI behavior with workspace manifest policies:

{
"ciPolicy": {
"requireCleanSync": true,
"failOnDrift": true
}
}
PolicyMeaning
requireCleanSync: trueCI must pass wl sync --check
failOnDrift: trueAny drift causes build failure

These policies inform Workloom's behavior but don't automatically enforce CI checks—you still need to add the wl sync --check command to your CI configuration.

Handling Failures

When wl sync --check Fails

$ wl sync --check --locked
# Workloom Sync (Check Mode)
# Workspace: My App
# Drift detected in 2 files:
# .claude/settings.json (state: owned_locally_modified)
# .claude/CLAUDE.md (state: owned_clean)
# Run: wl status
exit code: 4

Diagnose locally:

wl status
# Workloom Status Report
# Workspace: My App
# Drift detected in 1 file:
# .claude/settings.json (state: owned_locally_modified)
# Run: wl diff to see changes

See what changed:

wl diff
# .claude/settings.json
# --- desired
# +++ current
# (diff output)

Restore and Commit

If the drift is unintended, restore:

wl restore
# Restored .claude/settings.json to desired state

Commit the restored files:

git add .claude/
git commit -m "Restore AI configuration to desired state"
git push

The next CI run passes.

Review and Approve

If the drift is intentional (e.g., you've updated a trust gate asset), review and approve:

wl status
# Output lists which assets need approval

wl approve <asset-id>
# Approve the asset

wl pull
# Re-render with approval

git add .claude/
git commit -m "Update and approve AI configuration changes"
git push

Multi-Target Monorepos

For monorepos with multiple targets, check specific targets:

# Check all targets (default)
wl sync --check

# Check frontend app only
wl sync --check --target frontend

# Check multiple specific targets
wl sync --check --target frontend --target api

In CI, you might check only production-critical targets:

# GitHub Actions example
- name: Check production targets
run: pnpm wl sync --check --target api --target admin

Service Account Tokens (Phase 2)

In Phase 2, when Workloom has a hosted registry, use service account tokens for authenticated CI:

export WORKLOOM_SERVICE_ACCOUNT_TOKEN="wl_sat_..."
wl sync --check --locked

Service accounts have fine-grained permissions:

  • Read-only access to specific packages
  • No approval rights
  • Audit-logged access

Set up in GitHub Actions secrets:

env:
WORKLOOM_SERVICE_ACCOUNT_TOKEN: ${{ secrets.WORKLOOM_SERVICE_ACCOUNT_TOKEN }}

Preventing Accidental Changes

Lock File in Git

Always commit your lock file to version control:

# After updating your workspace, commit the lock
git add .workloom/lock.toml
git commit -m "Update Workloom lock file"

In CI, use --locked to enforce the locked state:

wl sync --check --locked
# Only checks against the lock file
# Rejects any manifest changes

Branch Protection

Configure your repo's branch protection to require the CI check:

GitHub: Settings → Branches → Require status checks to pass before merging → Select "Check AI configuration drift"

Azure DevOps: Repos → Branches → Branch policies → Add build policy → Select your pipeline

Pre-Commit Hook

Locally, catch drift before pushing:

#!/bin/bash
# .git/hooks/pre-commit

pnpm wl sync --check --locked
if [ $? -ne 0 ]; then
echo "Workloom drift detected. Run 'wl status' to review."
exit 1
fi

Troubleshooting

"Workspace binding not found"

The CI environment can't find the workspace binding (.loom/binding.toml). Ensure:

  1. The file exists in the repository
  2. Git-tracked (committed, not in .gitignore)
  3. Readable by the CI environment
git ls-files | grep binding.toml
# Should show: .loom/binding.toml

"Registry unreachable" (Phase 2)

In Phase 2, when fetching packages from the registry:

export WORKLOOM_REGISTRY_URL="https://registry.workloom.dev"
export WORKLOOM_SERVICE_ACCOUNT_TOKEN="wl_sat_..."
wl sync --check --locked

Verify network connectivity and credentials.

"Policy violation in CI"

A trust gate asset requires approval. In CI, this causes a policy violation:

# Output
# Workloom Sync (Check Mode)
# Policy violation: executable_hook asset requires approval
# Run: wl approve hook <id> locally, then push
exit code: 5

You must approve locally and commit the approval:

wl approve hook my-hook
git add .workloom/approvals.toml
git commit -m "Approve hook asset"
git push

Then rerun CI.

Performance

For large workspaces:

# With locked versions (fastest)
wl sync --check --locked
# Reads lock file, no network, deterministic

# Without locked versions (slower, fetches registry)
wl sync --check
# Fetches latest package versions (Phase 2+)

In Phase 1, both are equally fast since no remote registry exists.

Reporting Drift in Notifications

Custom notification on drift (GitHub Actions):

- name: Check drift
id: drift
run: |
pnpm wl sync --check --locked
echo "status=$?" >> $GITHUB_OUTPUT
continue-on-error: true

- name: Notify on drift
if: steps.drift.outputs.status != '0'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: ':warning: AI configuration drift detected. Run `wl status` and `wl diff` locally.'
})

Next Steps