Skip to content

Drift Detection

Drift is what you find between two apm install runs that should have been identical. This page is the operator’s checklist: the categories APM detects today, the command surface for each, and patterns for sweeping the signal across an estate of repos.

Four concrete categories. Each has a different signal and a different fix.

CategoryWhat it isDetected by
Lockfile driftapm.yml and apm.lock.yaml disagree, or deployed files do not match the lockfileapm audit --ci (8 baseline checks)
Cache driftA cached checkout’s HEAD no longer matches resolved_commit in the lockfileBuilt into every apm install cache hit
Ref drift (stale floats)A locked branch tip or tag has moved upstream since the last installapm outdated
Security-finding driftA primitive in apm_modules/ now contains hidden Unicode that was clean beforeapm audit

Marketplace-shadow drift — the same plugin name appearing under more than one registered marketplace — is reported inline by apm install when it happens; see security and supply chain.

The lockfile-consistency gate. Runs eight baseline checks in order and exits non-zero on the first failure (or on any failure with --no-fail-fast):

lockfile-exists -> ref-consistency -> deployed-files-present
-> no-orphaned-packages -> skill-subset-consistency
-> config-consistency -> content-integrity -> includes-consent

After the baseline passes, it replays the install in a scratch directory from the cache and diffs against the working tree to surface unintegrated, modified, and orphaned files. Pass --no-drift to skip the replay. With --policy <source> it also evaluates the discovered policy against the lockfile. Source: src/apm_cli/commands/audit.py, src/apm_cli/policy/ci_checks.py.

Scans every deployed primitive for hidden Unicode (zero-width characters, bidi overrides, tag characters) and runs the drift replay as advisory output. Exits 0 even when findings exist. Add --strip to remove hidden characters in place; add --file <path> to scan a single file outside the lockfile.

Compares each locked dependency against its remote tip. Tag-pinned dependencies use semver comparison; branch-pinned dependencies compare commit SHAs. Marketplace-sourced dependencies are compared against the marketplace entry’s current ref. Source: src/apm_cli/commands/outdated.py.

Terminal window
apm outdated # project lockfile
apm outdated --global # ~/.apm/ user-scope lockfile
apm outdated -j 8 # 8 parallel remote checks

apm outdated does not modify anything. It is the read-only view that tells you which floating refs have moved.

Prints the lockfile entry for a single package (resolved_ref, resolved_commit, deployed files). Use it to confirm what a repo actually has after a drift report names a dependency. Note that apm list lists scripts, not packages.

apm install verifies cache integrity on every cache hit before reusing a checkout. It reads .git/HEAD directly (not via git rev-parse, so a poisoned .git/config cannot subvert the check) and compares the resolved 40-character SHA against the resolved_commit recorded in apm.lock.yaml. On mismatch, the cache entry is evicted and a fresh fetch runs. Source: src/apm_cli/cache/integrity.py.

This means a stale CI runner, a teammate who manually git checkout-ed inside apm_modules/, or a bumped pin without a re-install all surface as either eviction-and-refetch (silent self-heal) or a hard failure when the cache cannot be repopulated — never as wrong content under the right name.

APM runs per repository. There is no built-in fleet console. The operational pattern is to run the same audit in every repo and centralize the output.

Two shapes work today:

Scheduled CI sweep. Add a workflow that runs nightly in every repo and uploads the SARIF or JSON output as an artifact (or to GitHub code scanning). The dashboard is whatever ingests the artifact.

# .github/workflows/apm-drift.yml -- nightly per repo
name: apm drift
on:
schedule: [{ cron: '0 6 * * *' }]
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: curl -fsSL https://aka.ms/apm/install | bash
- run: apm install
- run: apm audit --ci --format sarif --output apm-audit.sarif
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: apm-audit.sarif }
- run: apm outdated > apm-outdated.txt
if: always()
- uses: actions/upload-artifact@v4
with: { name: apm-drift, path: 'apm-*' }

Central audit harness. Run a single job that iterates over a list of repos (from a GitHub org listing or a static manifest), clones each one, and runs apm audit --ci --format sarif plus apm outdated. Aggregate the outputs into one dashboard.

Either pattern depends only on documented apm flags. APM does not phone home; the harness is yours.

apm audit --ci and apm audit both accept --format and --output.

FormatUse
text (default)Terminal review, pull-request logs
jsonCustom dashboards, ticketing integrations
sarifGitHub code scanning, Defender for Cloud, any SARIF-aware viewer
markdownPull-request comments, weekly status digests

--format json|sarif|markdown cannot be combined with --strip or --dry-run; those modes are interactive by design.

Once drift is detected, remediation routes back to two pages:

  • Enforce in CI — wire apm audit --ci into branch protection so future drift cannot land. The same command that detects drift here is the one that gates merges.
  • Security and supply chain — scope tokens, lock the registry proxy, and tighten apm-policy.yml so the drift you cleaned up cannot reappear from a new source.

For the consumer-side view of what these checks protect against on a single workstation, see drift and secure by default.