Skip to content
Back to skills

Migrate

ASecurity

Move instruction content to AGENTS.md as the one content home, keeping a one-line CLAUDE.md shim while a shim is what makes it load. Plans first; every write is operator-gated. Use when: 'migrate to AGENTS.md', 'plan the AGENTS.md migration', 'move CLAUDE.md content to AGENTS.md', 'add the AGENTS.md shim', 'our CLAUDE.md should be one line', 'share instructions with Codex and Cursor', 'can we drop the CLAUDE.md shims yet'. Not the placement sweep (audit) or applying findings (realign).

  • 17 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsgoshellbashgitdocumentation

Works with

  • claude code
  • cursor
  • cli

Security analysis

A92/100
  • mediumUses curl or wget to download content

Pro scans all 10 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add melodic-software/claude-code-plugins --skill migrate --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Migrate?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Migrate
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/melodic-software-migrate/badge)](https://www.skillsdirectory.com/skills/melodic-software-migrate)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
description: "Move instruction content to AGENTS.md as the one content home, keeping a one-line CLAUDE.md shim while a shim is what makes it load. Plans first; every write is operator-gated. Use when: 'migrate to AGENTS.md', 'plan the AGENTS.md migration', 'move CLAUDE.md content to AGENTS.md', 'add the AGENTS.md shim', 'our CLAUDE.md should be one line', 'share instructions with Codex and Cursor', 'can we drop the CLAUDE.md shims yet'. Not the placement sweep (audit) or applying findings (realign)."
argument-hint: "[plan|apply|cutover-check|remove-shims] [path ...]"
user-invocable: true
disable-model-invocation: false
allowed-tools:
  [
    "Bash(${CLAUDE_PLUGIN_ROOT}/skills/migrate/scripts/plan-migration.sh:*)",
    "Bash(${CLAUDE_PLUGIN_ROOT}/skills/migrate/scripts/cutover-check.sh:*)",
    "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/render-index.sh:*)",
    "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/glob-tools.sh:*)",
    "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/verify-load.sh:*)",
    "Read",
    "Grep",
    "Glob",
    "Edit",
    "Write",
    "WebFetch",
  ]
metadata:
  workflow-stage: implement
  summary: Move a repository's instruction content to AGENTS.md behind an operator gate
---

**Arguments.** `[plan|apply|cutover-check|remove-shims] [path ...]`. Default: plan the repository at the current root

# Migrate a repository to AGENTS.md

## Purpose

One tool-agnostic instruction source per repository, small enough to load every session, readable
by Codex, Cursor and every other agent that reads `AGENTS.md`, and still reaching Claude Code in the
sessions that cannot read `AGENTS.md` directly.

Two mutating skills live in this plugin. `/instruction-placement:realign` executes findings a
sibling audit produced, one at a time. This one owns a different unit of work: a repository's move
from `CLAUDE.md` to `AGENTS.md` as the content home, which is a decision about a whole instruction
layer rather than a list of findings. It gates every write on the operator just as `realign` does.

**A migration run never removes a shim.** Removal is a cutover with upstream conditions of its own,
and it is the `cutover-check` and `remove-shims` arguments below. Never delete a `CLAUDE.md` shim
during `plan` or `apply`, however unnecessary it looks.

## Invocation mode

`disable-model-invocation: false`, the fleet default. No exception class in the
[invocation-mode rubric](../../../../docs/conventions/invocation-mode/README.md) fits: this is not a
`setup` skill (class ii), not maintainer-only (class iii), and not class (i) either, since the human
already decides every write at the gate below rather than deciding the *timing* of an unattended
mutation. It is also a chain target: `/harness-memory:audit`'s N1 fix route and this plugin's own
`setup` remediation both point a repository here, and the invocation-reach invariant makes a `true`
skill unreachable from another skill.

## The target shape

- The root `CLAUDE.md` is exactly the one line `@AGENTS.md`. No heading, no comment, no other text.
- Every directory with a Claude-audience nested `AGENTS.md` has a sibling `CLAUDE.md` that is
  exactly `@AGENTS.md`.
- `AGENTS.md` carries the content. `CLAUDE.md` carries nothing but the import.
- `.cursor/`, `.codex/` and `.github/` belong to those tools. Their instruction files are never
  given a Claude shim and never merged into `AGENTS.md`.

## Where each kind of text goes

Each kind of text goes to the best current native location for it. That is the whole convention;
the three destinations below follow from it.

| Text | Destination | Why |
|---|---|---|
| Shared, relevant to **every** conversation | root `AGENTS.md` | It has to be resident, and every agent reads this file |
| Shared, relevant **sometimes** | the repository's **existing docs home**, behind a one-line pointer in `AGENTS.md` that says *when* to read it | It earns its place only at the moment it applies, and a resident copy taxes every session |
| **Claude-specific** | `.claude/rules/<topic>.md` with a `paths:` glob, or a stated always-relevant reason | Other tools never read it, and the glob is what makes the cost conditional |
| **Tool-agnostic but about one file or area** | Short: stays resident in root `AGENTS.md`. Long: the docs home, behind a pointer whose condition names the file. Scoped to one directory: a nested `AGENTS.md` | `AGENTS.md` has no path scoping, and `.claude/rules/` would hide it from the other tools that need it. Length decides between resident and pointer; a directory boundary is what makes the nested file fit |
| Another tool's | that tool's own directory, untouched | It is theirs |

The docs home is **detected, never imposed**: `plan-migration.sh` emits a `DOCSHOME` row naming the
directory the repository already keeps (`docs/`, `doc/`, `documentation/`). Use that one. Create
`docs/` only when the row says `absent`, and never relocate an existing home as part of a migration.

A pointer is not a summary. One line naming the file and the condition that should send a reader to
it ("Read `docs/release-process.md` before cutting a release") beats a paragraph restating it.

`/docs-hygiene:write-for-agents` owns how this text is written: invoke it via the Skill tool when
that plugin is installed, for the root `AGENTS.md`, every new `.claude/rules/` file, and every
pointer. Where it is not installed, write the text here and say that the write-side doctrine was
not available, rather than skipping the move.

## Plan first

```bash
${CLAUDE_PLUGIN_ROOT}/skills/migrate/scripts/plan-migration.sh --root <repo>
```

**Always pass `--root`.** A shell's working directory does not persist between tool calls, so a run
without it plans whatever directory the call happened to land in. `--root` may point anywhere inside
the repository; the script resolves to the toplevel itself.

Read-only: it has no other mode, and `--dry-run` is accepted and ignored so an older caller does
not break. A kind with nothing to report prints a
tab-separated `<KIND>` / `NONE` row, so a clean repository is visibly clean rather than
indistinguishable from a run that never happened. Its rows are the facts a migration turns on:

| Row | What it decides |
|---|---|
| `DIR` | `<path> <state> <CLAUDE.md bytes> <AGENTS.md bytes>`. The state names the work; the table below says what Apply does for each |
| `BUDGET` | `<path> <cumulative AGENTS.md bytes, root to that path> <OK\|OVER>`, against Codex's project-doc budget, which is cumulative across the files it loads rather than per file. The number and its dated record live beside `CODEX_PROJECT_DOC_BUDGET` in `scripts/plan-migration.sh`; read it there rather than restating it. `OVER` means content has to move out before the migration, not after |
| `CASE` | A filename differing only by case. Claude Code matches names exactly; NTFS does not, so the repository behaves differently per developer until it is renamed |
| `SUPPRESS` | A bare `~/CLAUDE.md` or `~/CLAUDE.local.md`. Present, it is read instead of `AGENTS.md` in every directory below home, and no repository-side change fixes that |
| `PATHDET` | Code that finds a path by the existence of `CLAUDE.md`. Each one works while the shim exists and breaks at cutover. Report them; fixing them is not this run's scope unless the operator asks. The match is **not** a list of existence-test spellings: a list misses `-e`, `Test-Path`, `os.stat`, `File.exist?` and whatever nobody thought of, and every miss reports a clean tree. Any existence-ish mention in a code file is a row, so prose in an eval file lands here too; that is the safe direction, and the acknowledgement file answers a false positive once, in writing |
| `CITE` | A markdown link resolving into `CLAUDE.md`. Each is retargeted in the same PR as the content move, or the link dies |
| `MENTION` | Every other tracked occurrence of the literal `CLAUDE.md`: a YAML list entry, a comment, a path in a config, including under `.claude/`, which is where a Claude-configured repo most often enumerates its own instruction files. Neither a link nor an existence call, so it is nobody else's row. A content directory holding more than ten is rolled up to `<dir>/ <count> rows`; `.claude/` and `.github/` never are, because a leak lives in configuration. `--expand-mentions` prints them all. **A roll-up is the answer, not a deferral**: a content directory (captured prose, vendored docs) is reported by count and left alone, because every one of its mentions is the same non-finding. Triage the individually-listed rows with the operator |
| `DOCSHOME` | Where a pointer target lands |
| `ACTION` | Every `claude-code-action` pin. Each decides the CLI version CI installs, and so whether CI reads `AGENTS.md` at all |

Present the plan, with the content split you propose per file, and stop. Nothing is written until
the operator accepts.

**Dispatched runs.** Where this skill runs under a brief that already states the accepted target
shape, that brief is the acceptance, and the plan output is still returned to the dispatcher with
the work. The worker triages the `MENTION`, `CITE`, `PATHDET` and `ACTION` rows and returns that
triage in its report; anything needing a write **outside** the accepted target shape is the
dispatcher's decision, not the worker's. This changes nothing for an interactive run: there, the
operator still accepts before anything is written.

**"Nothing to split" is a valid outcome, not a failure to find work.** A small root file whose
content is all every-conversation material stays whole in the root `AGENTS.md`. Steps 2 and 3 then
produce nothing, and no `docs/` directory and no `.claude/rules/` file is created. Say that plainly
rather than manufacturing a pointer or a rule to have something to show.

Likewise, **a repository with no Claude-specific text creates no `.claude/rules/` file and never
calls `glob-tools.sh`**. That is step 3 finding nothing to do, not step 3 being skipped; report it
as a result so nobody reads the absence as an omission.

## Apply

**The shortest path per `DIR` state.** Most directories need a fraction of the full sequence, and a
step with nothing to do is a result, not an omission:

| State | What Apply does |
|---|---|
| `agents-only` | **Create the shim only**: a `CLAUDE.md` containing exactly `@AGENTS.md`. Steps 1, 2, 3 and 5 are no-ops; step 6 still runs |
| `content-in-claude` | The full sequence: split the content, then create the shim where none existed |
| `both-with-content` | The full sequence, deciding per section which file it belongs in, then reduce `CLAUDE.md` to the import |
| `shim-with-comment` | **Strip the comment only.** The note moves into `AGENTS.md` or is deleted, the operator's call |
| `shim` | Nothing. Already the target shape; report it and move on |
| `shim-empty-target` | Nothing to move: `CLAUDE.md` is exactly `@AGENTS.md` and `AGENTS.md` is absent or empty (any other import form there is `content-in-claude`, so the full sequence rewrites it). Report it, and ask the operator whether the empty `AGENTS.md` is deliberate before writing to it. Do not run the split |
| `zero-byte` | Nothing to move. Ask the operator whether the empty files are deliberate before touching them |

The full sequence, one directory at a time, deepest last, in this order, because an interruption
between steps must leave content duplicated rather than deleted:

0. **Fix any `CASE` row first**, before anything else touches the tree. A filename differing only by
   case means the repository already behaves differently per developer, and every later step would
   be built on a file whose identity is not settled.
1. **Create or extend `AGENTS.md`** with the content that belongs there. Merge under a new heading;
   never clobber an existing file. **Moved text moves verbatim.** The hard rule below wins over any
   write-side advice: `/docs-hygiene:write-for-agents` governs text you are *writing new* here
   (pointer lines, a rules file's always-relevant reason, a new heading), never text you are
   relocating. Rewording moved text is a separate, later edit the operator asks for by name.
2. **Write the pointer targets** into the detected docs home, and the pointer lines into `AGENTS.md`.
3. **Write the `.claude/rules/` files.** Fetch the current rules frontmatter format at authoring
   time rather than writing it from memory: `curl -sL https://code.claude.com/docs/en/memory.md`
   and read its "Path-specific rules" section. Validate every glob with
   `${CLAUDE_PLUGIN_ROOT}/scripts/glob-tools.sh` before the file is written; a glob matching
   nothing is a rule that never fires, and nothing goes red.
4. **Write the shim**: `CLAUDE.md` containing exactly `@AGENTS.md`, one line. Create it where the
   directory had none (the whole job for an `agents-only` row), or reduce an existing one to that
   line once its content has a home. An HTML-comment note inside a shim is content: move it into
   `AGENTS.md` or delete it with the operator's say-so.
5. **Retarget every `CITE` row** in the same change. A link into `CLAUDE.md` whose content moved is
   a dead link the moment the move lands, so it is not a follow-up.
6. **Offer the index; never write it unasked.** The generated rules index is a *Claude-only* block
   in the tool-agnostic `AGENTS.md`, and it is not free: in `medley` the render was 6,860 bytes,
   +52% on the always-loaded file, pushing the worst Codex path to 28,870 of its 32,768-byte
   budget. So run `render` first, report the byte cost and the resulting worst-path `BUDGET` row,
   and write it only on the operator's acceptance:

   ```bash
   ${CLAUDE_PLUGIN_ROOT}/scripts/render-index.sh render --root <repo>
   ${CLAUDE_PLUGIN_ROOT}/scripts/render-index.sh write --file AGENTS.md --root <repo>
   ```

   It earns its cost where deferred surfaces are many and a worker would otherwise never learn they
   exist. It does not where the repository has a handful of nested files a reader meets anyway, or
   where the budget is already tight. **A repository that declines the index is not broken**:
   `check` reports `NO-BLOCK` and `index-drift` stays quiet, because a repository that never
   adopted an index is not drifting from one. With nothing to index at all, `write` says
   `NO-INDEX-NEEDED` and writes nothing; do not hand-write a block in either case.
7. **Run the repository's own markdown lint**, if it has one: check for a `lint:md` script in
   `package.json`, a `lefthook.yml` markdown hook, or a `.markdownlint*` config, and run what you
   find. A regenerated block or a moved heading can trip MD022 (blanks around headings), MD024
   (duplicate headings, likely when a section lands beside a similar one) or MD047 (single trailing
   newline). **A fresh worktree may need the repository's own install step first** (`npm ci`,
   `uv sync`, `dotnet restore`, whatever its README names). A wall of failures on a markdown-only
   diff is unprovisioned tooling until something shows otherwise: check that before reading it as
   the migration's doing, and never "fix" source you did not touch to make a linter start.
8. **Re-run the plan** and confirm the states moved the way the operator accepted.

Where a directory has a nested `AGENTS.md`, `render-index.sh wiring` says whether it needs a shim:
an `UNWIRED` row does, a `NATIVE` row does not.

**Before writing nested shims, look for a gate that classifies changed paths by directory prefix.**
A hook or CI rule keyed on `apps/`, `libs/` or `services/` reads `<dir>/CLAUDE.md` as code and
applies a code lane to it; in `medley` that tripped a runtime-affecting pre-push gate. Grep the
hook and workflow configs for those prefixes, and report each as an item for the operator. **The
fix belongs to the repository's gate, through its own tests.** Teach it that an instruction file is
not code. Never bypass the gate, and never rename or relocate a shim to dodge it.

**The rows Apply does not act on, and where each goes instead.** Every one is reported, none is
silently dropped:

- `PATHDET`: listed in the PR body as a cutover blocker. This skill changes none of them. Code that
  finds a path by the existence of `CLAUDE.md` keeps working while the shim exists, and rewriting it
  is the cutover's business.
- `ACTION`: listed in the PR body for the cutover check, which is what reads the pins.
- `SUPPRESS`: reported to the operator as theirs. No repository-side change reaches a bare
  `~/CLAUDE.md`.
- `BUDGET` with `OVER`: content moves out before the migration proceeds in that subtree, not after.

## The cutover: `cutover-check`, then `remove-shims`

The shim is worth its cost only while it is load-bearing. Whether it still is
is a question about upstream and about the fleet, not about any one repository,
so it is graded before anything is deleted.

### `cutover-check`, read-only

```bash
${CLAUDE_PLUGIN_ROOT}/skills/migrate/scripts/cutover-check.sh --repo <repo> [--repo <repo> ...]
```

Four graded conditions. Each prints `[MET]`, `[UNMET]` or `[UNREACH]` with the evidence behind it,
and the run exits non-zero unless every one is `[MET]`.

| Condition | What it reads |
|---|---|
| 1, the remote flag | The shipped bundle's code default for the flag, resolved at run time (the identifier is minifier-assigned and the offsets are per build, so neither is ever hardcoded), and the `env-vars` feature-flag list fetched live. Either the default being true or the AGENTS.md bullet being gone satisfies it |
| 2, CI | Every `claude-code-action` pin from `plan-migration.sh`'s `ACTION` rows, mapped to the CLI it installs and compared against the floor, plus the recorded CI canary |
| 3, the local canary | One metered `claude -p` turn per cwd, **with no tools available**, against a lone non-empty `AGENTS.md` in a scratch directory the script creates and removes: one under home, one on a second drive or path |
| 4, path detection | Every `PATHDET` row, matched against the repository's reviewed `.claude/cutover-pathdet-ack.txt` |

**A canary that can read files proves nothing.** With a `Read` tool and an `AGENTS.md` in the
working directory, a reply quoting that file is equally consistent with the model having read it
for itself. Both canaries here run `--tools ""`, so the session has no tools at all: instructions
load at session start, and a quotation from a session that *could* not read anything is evidence
of a load. Measured before adoption on 2.1.278: a scratch directory holding one `AGENTS.md` and
nothing else returned the token line with no tools, and a directory with no `AGENTS.md` answered
`NONE`.

**`[UNREACH]` is never a pass.** A probe that could not measure is not a condition that holds, and
the script treats an unreadable bundle, a restructured docs page, a pin outside the release map and
a canary that neither loaded nor cleanly refused all the same way.

**Condition 3 is a token canary, not `verify-load.sh`.** That script detects a load through an
`InstructionsLoaded` hook, and the hook does not fire for an `AGENTS.md` Claude reads directly,
which is exactly the surface this condition measures. `verify-load.sh` stays the instrument for
every **shimmed** surface, where the load arrives as an import and the hook fires.

**Condition 4 needs a human first.** A grep cannot tell "reads `CLAUDE.md` as an instruction file"
from "locates a path by its existence", and that distinction is the condition, so the judgment lives
in a reviewed file and the default fails closed. Each row of
`.claude/cutover-pathdet-ack.txt` is tab-separated `<path>` / `<the trimmed source line>` /
`<one-line reason>`, with `#` comments. Matching is on path plus exact source text, never line
number: a row that moves still matches, and a row whose code changed falls out and has to be
re-reviewed. An unacknowledged row is `[UNMET]` and is named, **and so is a row whose reason is
empty**: the reason is the review, and a path plus a copy of the line is only the match key.
**No path convention exempts anything, test trees included**: in this fleet the one real blocker
lives under `tests/`. `.claude/` is scanned like any other tree, because a hook or helper script
there locates a path the same way. Two things are out of the scan and the condition prints both:
markdown, which locates no path, and the acknowledgement list itself, whose every line quotes a
detector by design.

### Is the shim droppable here? Decide before `remove-shims`

`cutover-check` grades the build and the fleet, not whether this repository's users lose
instructions without the shim. Before offering `remove-shims`, read
[`reference/shim-droppable.md`](reference/shim-droppable.md) and grade its six conditions:
precedence, this machine's mode, the loader's state, the operator's answers for every other user
and surface, external `@` imports, and `InstructionsLoaded` dependents. It also gives the verdict for
nested `AGENTS.md` under each mode. **Unknown is failed**: any condition not shown to hold keeps the
recommendation at "keep the shim" and names it. This recommends, never removes.

### `remove-shims`, one repository per run

```bash
${CLAUDE_PLUGIN_ROOT}/skills/migrate/scripts/remove-shims.sh --root <repo> --confirm
```

**`--confirm` is the operator's word, never yours.** Add it only after the operator has named this
repository and said yes to it in this conversation: not pre-emptively, not because the check passed,
and never carried over from a yes given about a different repository. Run it without `--confirm`
first, show the operator what it prints, and wait.

**It is deliberately not in this skill's `allowed-tools`.** Every other script here is; this one
deletes files, so it takes a permission prompt every time rather than running on the skill's
standing grant.

It refuses far more often than it acts, and every gate fails closed. Without `--confirm` it prints
what removal costs and stops. With it, it refuses unless the **installed** `harness-memory` and
`instruction-placement` carry the corrected doctrine, read as the **lowest** version installed in
any scope, because the stale copy is the one that answers in the repository being de-shimmed (an
older cached build advises it straight back to the old shape), unless `cutover-check` reports every graded condition
`[MET]` in that same run, and unless every instruction directory is already at the target shape. A
zero-byte `AGENTS.md` cannot be canaried, so it is never de-shimmed.

Root and nested shims come out **together**: a lone nested `AGENTS.md` never attaches while a root
`CLAUDE.md` exists, so removing one without the other leaves files that review as correct and load
nothing.

**The nested canary runs from the nested directory, and its line has to be unique.** A session
started there loads that directory's `AGENTS.md` and every ancestor's, measured on 2.1.278 with no
tools available: running from the directory is itself the trigger, so no Read is needed. It also
means a line the nested file shares with the root file is answered by the **root** file, so the
nested surface would pass while loading nothing of its own. The canary line is therefore the longest
plain line **contained in** no line of any other `AGENTS.md` that loads beside it, ancestors above
the repository root included, and a file with no such line is refused **before** anything is
removed. Containment, not equality: the canary passes on a substring match, so an ancestor line
carrying the same sentence plus a clause answers the probe exactly as an identical one would.

After removal it runs one canary per de-shimmed directory against that line of
that directory's own `AGENTS.md`, so **no token is written into a real repository**, and any miss,
any canary that could not measure, and any failure part way through the removal restores every shim
the run removed. The restore writes the one import line back and verifies it byte for byte, rather
than asking git for an index copy a staged edit may have replaced; a restore that did not land says
so and the run still exits non-zero.

Removal is priced, and the price is printed before the confirmation: the hook-visible load goes,
and `/memory` becomes the only place to see the file
([`reference/sources.md`](reference/sources.md), "What shim removal costs"). That is a decision to
make, not tidying.

## Verify the load, never assume it

`verify-load.sh` drives one real `claude -p` turn with an `InstructionsLoaded` hook and prints
`VERDICT PASS|FAIL|UNKNOWN`. **`UNKNOWN` (exit 3) is a third outcome, never a pass**: rerun once,
escalate if it stays. A canary counts only against a non-empty `AGENTS.md`.

Four legs, every one run, none assumed: `verify-load.sh` per migrated surface; `render-index.sh
reachable --file AGENTS.md --root <repo>` captured **before and after**, which is the root-level
parallel of `wiring` and the whole load evidence for an `agents-only` repository (`NATIVE` before
the shim, `LOADED` after); a headless Claude canary; and a Codex canary where that CLI is
installed. The worked invocations, where the canary token goes, the Codex rollout check, and the
progressive-disclosure caveat are in
[`reference/verification.md`](reference/verification.md). Read it before running any of them.

## Why the shim stays

The shim is what carries `AGENTS.md` in two cases a repository cannot talk itself out of: a
`CLAUDE.md` above the file being read instead of it, and a session that cannot read `AGENTS.md`
directly at all.

This skill keeps the shim wherever a `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` can
shadow the `AGENTS.md` it carries, at the root or in a subdirectory, and wherever a session may
lack direct `AGENTS.md` support (the CLI floor is in `reference/sources.md`, "The minimum CLI
version"; the sessions that cannot read `AGENTS.md` directly are in `reference/shim-droppable.md`,
condition D). It treats the import as safe to keep: the shim never makes Claude read the file
twice. Canary runs on Claude Code 2.1.278 observed the shadowing; the 2026-09-29 pass did not
re-run that canary.

- **Pointer**: for when Claude Code reads `AGENTS.md`, when that support is unavailable, and what
  removing a shim involves, see
  <https://code.claude.com/docs/en/memory#when-claude-code-reads-agents-md>,
  <https://code.claude.com/docs/en/memory#when-agents-md-support-is-unavailable> and
  <https://code.claude.com/docs/en/memory#remove-an-earlier-agents-md-workaround>.
- **As of**: 2026-09-29
- **Recheck trigger**: those sections change which file names shadow `AGENTS.md` or which sessions
  lack support, or a release note names `AGENTS.md` or instruction-file loading.

It is priced, not free. A load through a shim keeps the `InstructionsLoaded` hook, which this
plugin's load verification depends on; a direct read loses it, and only recent versions list a
directly read `AGENTS.md` in `/memory`. That is a reason the shim is worth its ~55 tokens, and a
reason removing it later is a decision rather than tidying. The price, with its pointers, is in
`reference/sources.md`, "What shim removal costs".

Every upstream fact the cutover turns on lives as a dated pointer record in
[`reference/sources.md`](reference/sources.md): the remote flag and how its code default is read,
the documented feature-flag dependency, the CLI floor, the `claude-code-action` release to CLI map,
the CI canary result, the current fleet grade, and what shim removal costs. `cutover-check.sh`
parses the floor, the release map and the canary run out of that file rather than carrying its own
copy, and exits 2 on a record it cannot read: a fact it cannot parse is one it must not silently
skip checking. Read it before arguing about the shim from memory. One record there bears on verification today: `verify-load.sh`
detects a load through the `InstructionsLoaded` hook, so it measures a **shimmed** surface and
cannot see an `AGENTS.md` that Claude reads directly.

**One setting changes the reading, and no repository can ship it.** For an operator who sets
`instructionFiles: claude-md-and-agents-md`, the memory page does not say when a subdirectory's
`AGENTS.md` loads, so an `UNWIRED` row stays a finding there too and the nested shim stays; the
import stays harmless. The setting is not one a repository's project or local settings can carry,
so the gates keep the default's answer.

- **Pointer**: for the `instructionFiles` values and where the setting is read, see
  <https://code.claude.com/docs/en/memory#choose-which-instruction-files-load>.
- **As of**: 2026-10-01
- **Recheck trigger**: that table changes, the page comes to state when a subdirectory's
  `AGENTS.md` loads under that value, or a release note names the setting.

## Boundary, the built-in `cc-plugin-agents-md` plugin

One native Claude Code surface works on the same file, and the two are easy to conflate:

- **`cc-plugin-agents-md` (plugin-backed built-in)**: the built-in plugin the docs call
  `agents-md@builtin`. It reads `AGENTS.md` as project instructions where the project has no
  `CLAUDE.md`, and by its **Project instructions** (`instructionFiles`) option beside `CLAUDE.md`,
  not at all (`claude-md`), or with every project and user instruction file dropped
  (`managed-only`). It moves no file and writes no shim. Whether this build registers it, requires
  it in this session type, and gates it is read at run time from `/harness-ops:inventory`'s
  `builtin_plugins` lane, never assumed.
- **This skill**: moves a repository's instruction content into `AGENTS.md`, keeps the one-line
  `CLAUDE.md` shim, and decides when the shim can go.

**Routing.** The plugin is the loading mechanism this skill's shim decisions are judged against,
not a replacement for the migration. Where the plugin is enabled in this session, the shim still
carries `AGENTS.md` into the sessions the plugin does not reach; never treat the plugin's
presence on one machine as proof every session reads `AGENTS.md`; the droppability decision above
is where its state is read. Verdict `complementary`,
integration `route`; the four-part record is in
[`reference/sources.md`](reference/sources.md), "The built-in agents-md plugin".

**Mutation gate:** the plugin changes no file. This skill's moves and shim edits run only behind
its per-write operator gate.

## Hard rules

- **Never delete a `CLAUDE.md` shim outside `remove-shims`.** Reducing one to its import line is
  what `apply` does; deleting one happens only behind that script's gates, never by hand and never
  because a repository looks ready.
- **Never create or move instruction files into `.cursor/`, `.codex/` or `.github/`.** Another
  tool's instruction file is not an unshimmed Claude surface, and no Claude shim or moved
  instruction text belongs in those trees. That is the whole rule: a CI or tooling file that merely
  *enumerates* instruction-file paths (a `DOCUMENTATION_ROOTS` list, a lint glob, a docs gate) is in
  scope for the move, because the move is what breaks it. Edit it minimally, add the new path rather
  than rewriting the gate, and show it to the operator as its own item.
- **Never rewrite content while moving it.** A relocation whose diff also improves the prose is a
  diff nobody can review.
- **Create the destination before excising the source.** An interruption then duplicates content
  rather than losing it.
- **Never report a load verified without probing it.** `UNKNOWN` is not `PASS`, and a canary against
  an empty `AGENTS.md` proves nothing.
- **Never leave the index stale.** Regenerating it is part of the move.

## Spoke paths

The `reference/` files write the plugin's root directory as `<plugin-root>`, which is
`${CLAUDE_PLUGIN_ROOT}`. Put that path in place of the placeholder before running a command or
writing it into a brief. Those files arrive through the Read tool as plain bytes, so a `${…}` token
in them would reach the Bash tool unsubstituted, and the Bash tool's environment has no
`CLAUDE_PLUGIN_ROOT` to expand it from. Pointer: for where each `${…}` variable resolves, see
<https://code.claude.com/docs/en/plugins-reference#where-each-variable-resolves>. As of:
2026-09-30. Recheck trigger: that table adds supporting files to where a `${…}` reference resolves.

## Next

`/instruction-placement:check`. The gate that keeps every new `paths:` glob resolving and the index
in sync after the move.

## Gotchas

- **The root `CLAUDE.md` is what suppresses a nested `AGENTS.md`, so both move together.** A repo
  that adds nested `AGENTS.md` files while keeping a content-bearing root `CLAUDE.md` gets files
  that review as correct and load nothing.
- **`/init` writes `CLAUDE.md`.** Running it after a migration re-creates the content the migration
  moved out. Say so in the PR body of a repository whose contributors run it.
- **A prose shim is a finding, not a shim.** The plan converts a `CLAUDE.md` sentence asking for
  `AGENTS.md` into the one-line `@AGENTS.md` import, or drops the file where the session reads
  `AGENTS.md` natively; it never keeps the sentence beside the import. Pointer:
  [Remove an earlier AGENTS.md workaround](https://code.claude.com/docs/en/memory#remove-an-earlier-agents-md-workaround).
  As of: 2026-10-01. Recheck trigger: that section changes what a prose instruction does.
- **A committed symlink is not portable.** On a Windows checkout it materializes as a plain text
  file holding the link target, so the import is the form that works everywhere.
- **A `CLAUDE.local.md` one developer keeps silently turns `AGENTS.md` off for them.** It counts for
  the same check as `CLAUDE.md`, and no gate in the repository can see it.
- **Codex truncates past its project-doc budget**, which is one cumulative allowance across the
  files it loads, not a per-file cap (the dated record is beside `CODEX_PROJECT_DOC_BUDGET` in
  `scripts/plan-migration.sh`). A `BUDGET` row reading `OVER` means a Codex
  session in that directory is already losing instructions; fix it before the move, not after.
- **A `SUPPRESS` row is outside the repository's reach.** Nothing in a PR fixes a bare
  `~/CLAUDE.md`; report it to the operator as theirs to decide.

Files in this skill

  • SKILL.md30 KB
  • evals/evals.json7.6 KB
  • reference/sources.md9.5 KB
  • reference/verification.md6.9 KB
  • scripts/cutover-check.sh26.2 KB
  • scripts/cutover-check.test.sh24.6 KB
  • scripts/plan-migration.sh27.6 KB
  • scripts/plan-migration.test.sh24.7 KB
  • scripts/remove-shims.sh20.8 KB
  • scripts/remove-shims.test.sh24.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…