Find stale commands, paths and versions in AGENTS.md and CLAUDE.md
AGENTS.md & CLAUDE.md Linter (Context Drift), a VS Code extension by Zovo
For teams whose coding agents read AGENTS.md or CLAUDE.md, Context Drift flags the commands, paths and versions in those files that no longer match the repo.
Free, MIT license. Needs VS Code 1.90.0 or newer. Extension ID zovo.context-drift, version 0.2.3.

A real VS Code 1.142 window running Context Drift on test/fixtures/readme-example, a 4 line AGENTS.md with 5 stale claims.
- Catch renamed scripts, moved files and the wrong package manager before your coding agent trips on them.
- Find
@importsin CLAUDE.md and GEMINI.md that point to missing files, and AGENTS.md files too big for Codex's 32 KiB limit. Both tools skip that text without a warning. - A quick fix replaces a wrong script, path or package manager in one click, and "Apply all" fixes the whole file.
- It runs locally with no network and no account, and the same check runs in CI.

Real VS Code 1.142 window on a generated sample repo: two broken @imports in CLAUDE.md (CD007), a missing script in a .claude/rules file (CD001) and an AGENTS.md over Codex's 32 KiB limit (CD008).
Quick start
- Install Context Drift and open a folder that has an
AGENTS.md,CLAUDE.mdor other agent instruction file. Findings show up as squiggles and in the Problems panel. - Put the cursor on a finding and press
Ctrl+.(Cmd+.on macOS) to fix it.

Recorded in the same window. The white arrow is drawn in to show where each click lands. The 2 findings left (a missing Makefile target and the Node version) have no one-click fix.
For a short tour, run "Context Drift: Get Started" from the Command Palette.
Questions people ask
Is there a linter for AGENTS.md?
Context Drift checks whether AGENTS.md is still true for your repo. It flags npm scripts and make targets that no longer exist, paths that moved, the wrong package manager and outdated Node, Python or Go versions. For the file format itself (frontmatter, rule schemas), use agnix. The two fit together.
Which instruction files does it read?
AGENTS.md, CLAUDE.md, CLAUDE.local.md, GEMINI.md, .cursorrules, Cursor .mdc rules, .github/copilot-instructions.md, Copilot .instructions.md files, Windsurf rules, .clinerules and SKILL.md.
How do I fix a finding?
Put the cursor on the squiggle and press Ctrl+. (Cmd+. on macOS). A quick fix swaps in the right script, path or package manager. When a file has two or more fixes, "Apply all" does them in one edit.
Can I run it in CI?
Yes. The same engine runs as a CLI with npx -y github:theluckystrike/context-drift. Use --format github to get inline annotations on the pull request. Pin a tag so a later change can't alter your CI.
How many findings are false alarms?
It's built for precision. On the repositories we measured, about 9 in 10 reported findings were true. It skips negated, historical and example text, so it misses some real drift.
Does it send my code anywhere?
No. It runs locally, makes no network requests and has no telemetry. It's free and MIT licensed.
Why
Agent instruction files state facts about your repo, like which commands to run, where files live, and which package manager and runtime to use. Code moves on and these files don't. Then the agent runs a script that no longer exists, opens a file that moved, or installs with the wrong package manager.
Context Drift compares what your instruction files say with what is actually in the repo. It shows each mismatch in the editor, with the exact range and a quick fix where one is cheap.
It reads AGENTS.md, AGENTS.override.md (Codex), AGENTS.local.md and AGENT.md, CLAUDE.md, CLAUDE.local.md, .claude/rules/**/*.md, GEMINI.md, .cursorrules, .cursor/rules/**/*.mdc, .github/copilot-instructions.md, .github/instructions/**/*.instructions.md, .windsurfrules, .windsurf/rules/**/*.md, .devin/rules/**/*.md, .kiro/steering/**/*.md, .clinerules (file or folder), and SKILL.md.
It's built for precision over recall. It skips text that reads as negated, historical, hypothetical or an example, and paths it can't check, so it misses some real drift. On the repositories we measured, about 9 in 10 reported findings were true (see Precision). We haven't measured recall on real files.
Before / after
AGENTS.md in a repo that moved from pnpm to npm, renamed its deploy target, moved main.ts, and upgraded Node. Below is the real output on test/fixtures/readme-example.

Drawn from real output on test/fixtures/readme-example. The editor half is a drawing made from the CLI's JSON output. The CLI half is its real text output.
- Requires Node 18.
- Install with `pnpm install`.
- Build: `npm run buidl`. Deploy: `make deploy`.
- Entry point: `src/server/main.ts`
$ npx -y github:theluckystrike/context-drift
AGENTS.md:1:12: warning CD004 Version claim "Node 18" does not match the repo: .nvmrc says 20.11.0.
AGENTS.md:2:17: warning CD003 `pnpm` is used here, but the repo uses npm (package-lock.json).
AGENTS.md:3:19: error CD001 Script "buidl" is not defined in package.json. Did you mean "build"?
AGENTS.md:3:41: error CD001 `make deploy`: no target "deploy" in Makefile.
AGENTS.md:4:17: warning CD002 Path "src/server/main.ts" does not exist in the repo. Did you mean "src/main.ts"?
context-drift: 1 instruction file(s) checked in 9 ms: 2 error(s), 3 warning(s), 0 info
Here's the file after the quick fixes (and picking deploy-prod from the Makefile).
- Requires Node 20.
- Install with `npm install`.
- Build: `npm run build`. Deploy: `make deploy-prod`.
- Entry point: `src/main.ts`What it checks
Each finding has a stable rule id, a severity, and an exact range.
CD001 command drift
It's an error for npm run X, pnpm run X and make X checked against the manifest in the right folder. Every other form is a warning (npm test, bare pnpm X / yarn X / bun run X, workspace-wide --filter/-r, just, task, cargo, uv run, a Makefile found only in a parent folder).
Commands it checks
npm run X,npm test,npm start,pnpm X,yarn Xandbun run XwhereXisn't inscripts. It checks the nearestpackage.jsonand the root one. With--filter,-r,-wor--workspaceit checks every workspace package. If the script exists in another workspace package, or in apackage.jsonabove the opened folder (up to the git root), it reports nothing. Binaries from dependencies count (yarn vue-cli-servicefrom@vue/cli-service).make Xwith no targetXin the Makefile, includingincluded files,-C dirand-f file. Makefiles that generate targets ($(eval ...), pattern rules) and targets defined in a Makefile further down the tree aren't reported.just Xwith no recipe or aliasX, andtask Xwith no taskXin the Taskfile.cargo -p crateandcargo --bin namefor crates and binaries that don't exist.cargo Xwhen the repo defines[alias]but notX.uv run Xandpoetry run XwhenXisn't in[project.scripts]or[tool.poetry.scripts]and isn't a declared dependency.
How it reads commands
- It understands
cd dir && …, environment prefixes (FOO=1 …), and$prompts. Inconsoleblocks, and in shell blocks that use$prompts, only prompt lines are commands; the rest is output.make test Run the testslistings stop at the description. - It doesn't flag built-ins (
npm install,pnpm add,bun test,cargo build, …) or placeholders (npm run <script>).
Negated and historical text
It skips negated or historical text. The words no, not, never, don't, avoid, instead of, deprecated, removed, old (and similar) in the clause before the command, or on the previous line of the same paragraph, turn the check off, as in "There is no root pnpm dev" or "don't run npm run eject". A new clause starts in four places.
- After until, unless, before, but, then. "Do not open a PR until
make checkpasses" is checked. - After a comma, when two or more words follow it, or an imperative such as run, use, call, regenerate. "Never edit generated files, run
npm run codegeninstead" is checked. A list ("a,borc") and an aside after a bare negation ("Do not, under any circumstances, runX") stay negated. - After the comma that ends an opening instead of, unlike, rather than phrase. In "Instead of
npm run old, usenpm run new" and "Unlikenpm run old,npm run fullruns everything" the second command is checked. - After and, when three or more words follow it, or when an imperative follows four or more words. "Do not push to main and run
npm run lintbefore merging" is checked, "Never build and runX" isn't.
A negation followed by without, forget, fail to, skip, omit, ignore is a requirement only when that word governs the command directly. So "Never commit without running npm run lint", "Don't forget to run npm run typecheck" and "Do not skip npm run typecheck" are checked. In "Do not skip tests by calling npm run skip-tests" the command is the forbidden means, so it stays skipped. Words after the command ("is deprecated", "(removed)", "no longer works") count only when the command is their subject.
These are word rules, not a grammar. Unusual sentences can still be misread, in either direction.
It also skips commands that the text says run somewhere else ("inside its package directory", "from the workspace root", "from the library"). In a sparse checkout it skips workspace-wide forms, because the packages may not be checked out. If the instruction file is a skill (SKILL.md), this rule is off, since skills usually describe other projects.
CD002 path drift
Reported as a warning (globs as info).
- Backticked relative paths (
src/foo.ts,./docs/x.md,folder/,.\scripts\build.ps1) and markdown links that don't exist. - Module-style paths without an extension count as present when a file with a usual extension or an
indexfile exists (src/utils/format→src/utils/format.ts,pages/api/hello)../utilsinpackages/x/AGENTS.mdmay also meanpackages/x/src/utils. - It suggests a fix when exactly one file with the same name exists elsewhere, which is usually a moved file.
- Globs that match nothing (
src/**/*.vue) are reported as info, including Cursor.mdcglobs:and CopilotapplyTo:patterns that never apply. A glob whose last part has no extension (packages/*/src) also matches folders. At most 200 globs per file are checked.
What it ignores
It ignores URLs, ~/ and absolute paths, and paths right after an external URL or an owner/repo name (they live in that other repo). It also skips ../ paths that climb above the opened folder (../shared/config.ts in a monorepo package opened on its own). They may exist outside it, so they aren't checked. Placeholders (<name>, path/to/, my-app, your_tool.py, mycommand.go) are skipped too.
Gitignored paths and globs are skipped. Each .gitignore in the repo and .git/info/exclude are matched against the whole path, wildcards included, and when git is allowed, git check-ignore has the last word. .gitignore negation (!folder/) isn't modeled, so a path under a re-included folder is treated as ignored and not reported.
It skips build output and scratch folders (dist, build, out, cjs, generated, …), tool state folders such as .scratchpad/, paths inside git submodules, code fences, and anything under an "Example" heading.
Text that names, creates, generates or stores a file rather than claiming it exists is skipped too ("a file named X", "make sure X/ exists", "regenerate X", "stored in X"). Generic words such as new, like and into count only within the 4 words before the path, so "New API handlers go in src/api/handlers.ts" is checked.
Special folders
- In a sparse checkout, a missing path is reported only when
git ls-filesconfirms it isn't in the repo. Without git, missing paths aren't reported there. - If the folder isn't a git repository root, has no manifest, and most referenced paths are missing, it reports one info "Not a project root" instead of a list of missing paths. That's the usual case for an instruction file copied somewhere else.
- In
SKILL.mdit checks only the skill's own bundle, which means links andscripts/,references/,assets/and similar paths, resolved against the skill folder and the plugin root.
CD003 package-manager drift
Reported as a warning.
- The file says
pnpm,yarn,bunornpm, in a command or in prose ("we use yarn"), butpackageManageror the lockfile says another. - The quick fix replaces the command, for example
npm run dev→pnpm run dev. - Global installs (
npm i -g) are fine.
CD004 version drift
Reported as a warning.
"Node 18", "Python 3.11+", "Go 1.22", nvm use 18 and pyenv local 3.10 are checked against .nvmrc and .node-version, engines.node and volta.node, .python-version and runtime.txt, requires-python and Poetry's python, go.mod (go and toolchain), and .tool-versions.
- In prose, "Node" must be capitalized (or written
Node.js,nodejs, "node version"), so "node 7 of the cluster" isn't a version claim. - A package-level instruction file is compared with its nearest
package.json, not with the workspace root. - It understands ranges (
>=18 <21,^20,~=3.11) and version matrices ("Node 18, 20 and 22"). - Historical, feature and CI text is ignored, such as "dropped Python 3.8", "added in Go 1.23", "Node 18 is too old", "CI runs Python 3.14".
CD005 cross-file conflict
Reported as info.
AGENTS.mdsaysmake testandCLAUDE.mdsaysnpm test.- Two instruction files in the same scope give different commands for the same verb, such as test, build, lint, format, dev or typecheck.
- Files in subfolders are a separate scope, and commands that run in different folders (
npm testvscd api && pytest) don't conflict.
CD006 stale file
Reported as info, and off by default.
- Turn it on with
--stale(CLI) orcontextDrift.gitStaleness(editor). - The instruction file's last commit is older than 180 days, and the repo has 50 or more commits since then.
- It uses
git log, read-only, with a 3 s timeout per call. It's skipped silently when there's no git and in untrusted workspaces.
CD007 import drift
Reported as a warning.
- Claude Code loads
@pathlines inCLAUDE.mdandCLAUDE.local.mdas imports, and Gemini CLI does the same inGEMINI.md. The path is relative to the file that imports it. - When the file isn't there, both tools skip the import without a warning, so the agent never sees those instructions. A common case is
@AGENTS.mdor@docs/testing.mdafter a rename. - A quick fix offers the one file in the repo with the same name, or a close spelling in the same folder.
- It only checks tokens that look like files: an extension such as
.mdor.json, or a leading./or../. Handles (@alice), scoped packages (@types/node), email addresses,@~/and absolute paths, globs, code spans, code blocks and gitignored files are left alone.
CD008 Codex size limit
Reported as a warning.
- Codex reads at most 32 KiB of
AGENTS.mdtext by default (project_doc_max_bytes) and stops adding instructions at that size. - An
AGENTS.mdorAGENTS.override.mdover 32 KiB gets one finding on the line where Codex stops, with the file size and how much is cut. - Raise
project_doc_max_bytesin~/.codex/config.toml, or move parts intoAGENTS.mdfiles in subfolders, which Codex reads only when it works there. - It checks one file at a time. Codex also counts the
AGENTS.mdfiles above the current folder toward the same 32 KiB, so a smaller file can still be cut.

Suppressing a finding
<!-- context-drift-disable-next-line CD002 -->
- `generated/schema.ts` appears after `npm run codegen`.
<!-- context-drift-disable-next-line CD001 --> (above a code fence: the whole block)
```bash
npm run legacy-task
```
<!-- context-drift-disable CD006 --> (whole file)
Directives count only as real HTML comments. Text in a code span that shows the syntax doesn't turn anything off. In YAML frontmatter (Cursor .mdc), use a YAML comment such as # context-drift-disable-next-line CD002. The quick fix picks the right form.
Format linting is agnix's job
Context Drift doesn't lint the format of instruction files (frontmatter, rule schemas, hook and MCP config, and so on). Use agnix for that. It has hundreds of lint rules for Claude Code, Codex, Cursor, Copilot and more. The two tools fit together. agnix checks that the file is well-formed, and Context Drift checks that it's still true. Run both.
Editor
- Diagnostics appear when you open, edit (debounced) or save an instruction file. They also refresh when
package.json, a Makefile, a lockfile,.nvmrcand similar files change, and when files are created or deleted. Saving any other file does nothing. - The checks and git run in a worker thread, never on the extension host. The repository view is cached. An instruction-file edit reuses it, a manifest change re-reads only the manifests, and only created or deleted files make it walk the folder again. The engine stops after 20 s and returns what it has, and lists the files it didn't check. The worker is stopped when one folder's check runs longer than 30 s, counted from when the worker starts that check. In a multi-root workspace, the other folders' checks that this drops are run again.
- Run the command "Context Drift: Check Workspace" to check everything.
- The status bar shows
Drift: N(warnings and errors). Click it to re-check. - Run "Context Drift: Get Started" to open the walkthrough.
Quick fixes can replace a script, target or path with the suggestion, or replace the package manager. When a file has two or more of these fixes, "Apply all N Context Drift fixes in this file" applies them in one edit. It shows only while the file is unchanged since the last check, so it never applies a fix at an old position. After you type, it comes back when the re-check finishes (about half a second). It leaves out a fix that overlaps another one. They can also show scripts in package.json, open the Makefile, or reveal the referenced file's folder. And they can disable a check for this line (or code block) or ignore a rule in this workspace.
Settings
| Setting | Default | Description |
|---|---|---|
contextDrift.enable | true | Turn the checks on or off. |
contextDrift.ignoreRules | [] | Rule ids to skip, for example ["CD005", "CD006"]. |
contextDrift.useGit | true | Use read-only git check-ignore and git ls-files to skip ignored paths and files a sparse checkout leaves out. 3 s timeout per call. Always off in untrusted workspaces. |
contextDrift.gitStaleness | false | Turn on CD006 (git log, read-only). Always off in untrusted workspaces. |
contextDrift.staleDays | 180 | CD006 age threshold, in days. |
contextDrift.staleCommits | 50 | CD006 commit threshold. |
contextDrift.askForRating | true | Ask one time for a Marketplace rating, after 5 checks and 2 days. Turn it off with false |
CLI
The CLI isn't published to the npm registry. Run it from GitHub with npx, or from a clone.
npx -y github:theluckystrike/context-drift # check the current repo
npx -y github:theluckystrike/context-drift path/to/repo # or a repo / a single instruction file
npx -y github:theluckystrike/context-drift --json # machine-readable
npx -y github:theluckystrike/context-drift --format github # GitHub Actions annotations
npx -y github:theluckystrike/context-drift --fail-on warning --ignore CD005
# or: git clone https://github.com/theluckystrike/context-drift && node context-drift/bin/context-drift.js .
Don't run npx context-drift without the github: prefix. There's no npm package by that name, and someone else could publish one.
The options are --json, --format text|json|github, --fail-on error|warning|info|never, --ignore <ids>, --stale (turn on CD006), --no-git (never run git), --stale-days, --stale-commits, and -q. Files that aren't checked (over 1 MB, a symlink that leaves the folder, lines over 4,000 characters) are listed on stderr.
The exit code is 0 when there are no findings at the --fail-on level (default error), 1 when there are findings at that level, and 2 on a usage error.
GitHub Action
Pin a tag, so a later change in the repo can't change your CI.
name: context-drift
on:
pull_request:
paths: ["**/*.md", "**/.cursorrules", "**/*.mdc", "package.json", "Makefile", "justfile", "pyproject.toml", "go.mod", ".nvmrc"]
concurrency: { group: context-drift-${{ github.ref }}, cancel-in-progress: true }
jobs:
drift:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npx -y github:theluckystrike/context-drift#v0.1.3 --format github
--format github prints ::error, ::warning and ::notice workflow commands, so findings show up inline on the PR diff. Add --stale and fetch-depth: 0 on the checkout step if you want CD006.
Speed and safety
It has zero runtime dependencies. One small CommonJS engine is shared by the extension and the CLI. A 5,000-file repo takes about 20 ms on an M-series Mac. It never reads node_modules, .git, dist, build, vendor, target or .venv, and it skips nested git repos and worktrees.
Hostile repos
Globs from the repo are never turned into regular expressions. A linear-time matcher handles them instead, and every line regex is linear. Inputs are capped at 1 MB per file, 4,000 characters per line, 200 globs, 20,000 code spans, 2,000 findings per file and 32 targets per make command. Work stops at the finding cap, and a run stops after 20 s.
Tests time the adversarial inputs from both independent reviews at about 1 MB. The first review's must take under 250 ms of CPU each (they take 1–50 ms, and the quadratic versions took 5 s and more). The second review's must take under 300 ms (the slowest takes about 70 ms on an M-series Mac). Other inputs can be slower, and the 20 s budget bounds them.
- Text output (
--format text) and diagnostic messages escape control and bidi characters from the repo as\uXXXX. In text output, line breaks and tabs in file names and messages are escaped too, so each finding is exactly one line.--format githubpercent-encodes line breaks,%,,and:. So a hostile file name or path can't rewrite your terminal or print its own line (such as a forged::errorcommand) into a CI log. - git runs only asynchronously, read-only, with
--no-optional-locks,core.fsmonitor=falseand a 3 s timeout per call. A hung git is killed. - Binary, huge, CRLF, BOM, Unicode and broken (
package.jsonwith comments) files never crash it.
Precision
Measured for 0.1.1 on the same material as the independent review of 0.1.0 (details in NOTES.md in the repository). 0.1.2 gives exactly the same warnings and errors on the 184 local folders below (re-run on 2026-11-05, none added, none removed). We compared 0.1.3 with 0.1.2 on the same 184 folders (2026-11-05) and got the same 264 findings, none added, none removed.
- Real source repositories from the review (a webdriverio checkout, promptbook, a small CLI repo) gave 4 findings, all true. 0.1.0 reported 4 true and 14 false findings there.
- 184 local folders with 3,629 instruction files (git off, as in the review). Overall, about 1 finding in 10 is false. CD001 gave 6 findings, 1 false (0.1.0 gave 31 findings, 26 false). CD004 gave 3 findings, none false (0.1.0 gave 8, 5 false). CD005 gave none (0.1.0 gave 4, all false). CD002 gave 224 warnings (0.1.0 gave 921). In a random sample of 40, 4 were false. Another 21 are technically true, but they sit in folders that hold a copy of an instruction file without the source.
- All 48 adversarial cases from the review pass. 0.1.0 had 24 false positives and 1 false negative.
- Of the 53 cases of the second review (0.1.1), 50 pass (0.1.1 passed 42). Of its 17 recall cases, 0.1.2 reports the expected finding in all 17, where 0.1.1 found 10. The three cases that don't match are known. They're
.gitignorenegation (not modeled), a span with terminal control characters (not read as a command), and one case where the review's own expectation was wrong ("Go 1.20 or newer" againstgo 1.22is true drift and is reported). These cases are regression tests intest/review-r2.test.js. - Of the 51 cases of the third review (0.1.2), 0.1.3 passes 42 (0.1.2 passed 34). The 9 that don't match are known false negatives, and none of them is a false finding.
npm run foo/barare placeholders and never checked (the same sentence with real names passes). add, write, may anywhere in a sentence still hide a path (3 cases, precision first). Include/import cycles in Makefile, justfile and Taskfile (3 cases) and"scripts": nullreport nothing, and amaketarget with control characters isn't read as a target. These cases are regression tests intest/review-r3.test.js.
Privacy
Context Drift runs locally. It makes no network requests and has no telemetry. It reads files inside your workspace only, and a symlinked instruction file or manifest that points outside the workspace is skipped. In trusted workspaces it may run read-only git (check-ignore, ls-files, and log when CD006 is on). Nothing leaves your machine.
License
MIT © 2026 Zovo
Install AGENTS.md & CLAUDE.md Linter (Context Drift)
Click the button to open VS Code and install it, or press Ctrl+P (Cmd+P on macOS) in VS Code and run ext install zovo.context-drift.
Free, MIT license. Needs VS Code 1.90.0 or newer. Extension ID zovo.context-drift, version 0.2.3.
Source code and issues are on GitHub.
Support this work
If this extension saves you time, you can support the work on GitHub Sponsors. Monthly tiers start at $5, and one-time tiers start at $15. Sponsorship is optional, and the tool works the same without it.