Notes on access-control bugs and AI-agent instruction drift, from the people who built entitlement-guard and baton to catch them.
Multi-agent setups — Claude Code, Codex, or any workflow built around
CLAUDE.md/AGENTS.md-style convention files — share a habit: they keep
a “here’s the state of the world” file that every new agent session is
supposed to read before acting, and every finishing session is supposed
to update. In practice, the update step is the one that slips. Real
commits land, the memory file doesn’t get touched, and the next agent
session starts from a picture of the repo that’s already wrong.
This isn’t a hypothetical. It’s independently documented as the same failure mode by people who aren’t us: dev.to has “Your AGENTS.md Is Already Stale — And Your Agent Trusts It Completely,” and nuvikatech.com has “The Clone That Didn’t Get the Memo: Worktree CLAUDE.md Drift” — both 2026, both describing a stale instruction file that an agent trusted anyway, because nothing told it not to. The framing that stuck with us: a stale entry is worse than a missing one, because it resolves ambiguity in the wrong direction with full confidence.
Once you accept the file rots, you need something checking it against reality. There are two structurally different ways to build that checker.
LLM-driven. ClaudeForge
ships a claude-md-drift-audit skill that runs as a forked Claude Code
subagent (context: fork, agent: Explore): it walks recent git history,
greps the CLAUDE.md files in the tree, and has the model synthesize a
punch list of stale references. It’s genuinely useful, and it’s only
available inside a Claude Code session — every audit is a live agent
invocation, which means it costs tokens per run and can’t be dropped into
a generic CI job as a standalone binary with an exit code.
Deterministic. We built
baton as the other kind: a plain
CLI, regex- and git-log-based, no API calls, no tokens, no network
dependency beyond git itself. It reads the state file’s own
## Last Updated claim, looks at every commit since that date, and
reports:
Last Updated never mentioned
anywhere in the file.Last Updated vs. a configurable threshold.It exits 1 on drift, so it works as a CI gate the same way a linter
does — same behavior on a laptop, in CI, or in an air-gapped environment,
since there’s no model call to route through.
They’re not really competing for the same moment. If you’re already
inside a Claude Code session and want a model’s read on what’s stale,
claude-md-drift-audit is a reasonable ad hoc check. If you want a gate
that runs identically in CI on every PR, with zero marginal cost per run
and no dependency on which agent harness happens to be active, that’s
what baton is for:
git clone https://github.com/Ayinla10/baton && cd baton
npm install && npm run build && npm link
baton check --fail-on-drift
Free, MIT-licensed, and it was built to solve our own problem first — we run a multi-agent setup with a shared state file, and this is the tool that stopped us from feeding stale context back into every new session.