Back to What 7,370 popular GitHub repositories put in AGENTS.md and CLAUDE.md
Methodology

What 7,370 popular GitHub repositories put in AGENTS.md and CLAUDE.md · Methodology

A census, not a sample: every public GitHub repository with 5,000+ stars, not a fork or archived, pushed to in the 90 days to 27 September 2026. We read each root AGENTS.md and CLAUDE.md (root or .claude/), following symlinks, and seven other instruction files.

The population

The study is a census, not a sample. It covers every public GitHub repository that, on 27 September 2026:

  • had 5,000 or more stars,
  • was not a fork and was not archived, and
  • had received a push in the previous 90 days, that is since 29 June 2026.

In GitHub's repository search syntax that is stars:>=5000 fork:false archived:false pushed:>=2026-06-29. We chose it to cover the projects most visible to developers and most likely to be worked on with coding agents: popular enough to matter, active enough to be current. It says nothing direct about private code or smaller projects.

GitHub's search API returns up to 1,000 results for each search, so we split the star range into bands small enough that no band held more than 900 repositories, and paged through each band sorted by stars. The search reported 7,370 matching repositories and we listed 7,370. All 7,370 could be read.

What we read

For each repository we read, through GitHub's GraphQL API, the default branch as it stood on 27 September 2026:

  • the list of files and folders at the root, with each entry's git mode, which shows symlinks;
  • the text of AGENTS.md and CLAUDE.md at the root, and of .claude/CLAUDE.md;
  • whether these exist: GEMINI.md, AGENT.md, .github/copilot-instructions.md, .cursorrules or a .cursor/rules directory, .windsurfrules or a .windsurf/rules directory, .clinerules, and .junie/guidelines.md;
  • the commits that touched AGENTS.md and CLAUDE.md on the default branch.

We did not read files in subdirectories. A monorepo whose AGENTS.md files live in its packages counts as having none unless it also has one at the root.

Definitions

  • Has an AGENTS.md. A file at the root whose name is AGENTS.md in any letter case. 14 use another case, such as agents.md; Codex looks for the exact name AGENTS.md, so on a case-sensitive file system it would not find them.
  • Has a CLAUDE.md. CLAUDE.md at the root or .claude/CLAUDE.md, the two places Claude Code reads a project's shared instructions from. 1,347 repositories have it only at the root, 65 only in .claude/, and 3 in both. Where both exist, length and content describe the root file. CLAUDE.local.md is personal and usually not committed, so we did not look for it.
  • Symlinks. When AGENTS.md or CLAUDE.md is a symlink, we followed it, through further links if necessary, to the file it points at and measured that file. 88 AGENTS.md files and 305 CLAUDE.md files are symlinks.
  • Any instruction file. Any of the files above.
  • Language. The primary language GitHub reports for the repository. The report charts languages with at least 150 repositories.

How CLAUDE.md relates to AGENTS.md

Claude Code's documentation says it reads AGENTS.md by default only when there is no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the working directory or above it, and that CLAUDE.md can import AGENTS.md with an @AGENTS.md line; import parsing skips Markdown code spans and fenced code blocks. So for every repository with both files we asked whether CLAUDE.md carries the content of AGENTS.md. The tests run in this order, and the first that matches decides:

  1. CLAUDE.md is a symlink that resolves to AGENTS.md.
  2. AGENTS.md is a symlink that resolves to CLAUDE.md.
  3. Both are symlinks that resolve to the same third file.
  4. CLAUDE.md contains an import of AGENTS.md (@AGENTS.md, @./AGENTS.md, or from .claude/, @../AGENTS.md) outside code spans and code blocks.
  5. The two files have the same text once runs of whitespace are collapsed.
  6. CLAUDE.md contains the whole text of AGENTS.md, which must be at least 200 characters.
  7. CLAUDE.md is under 600 bytes and mentions AGENTS.md: a pointer that tells Claude to read it.
  8. Otherwise the files are separate.

The first six carry AGENTS.md into what Claude Code loads by default; the seventh leaves it to the model to open the file; the eighth does neither. Where a repository has CLAUDE.md both at the root and in .claude/, the stronger relation counts. For separate files we also record whether CLAUDE.md mentions AGENTS.md anywhere. The rules, exactly as the analysis script applies them:

# How a CLAUDE.md relates to the repository's AGENTS.md. The first six carry
# AGENTS.md's content into what Claude Code loads; "points" leaves it to the
# model to open the file; "separate" does neither.
LOADS_AGENTS = (
    "claude_symlinks_to_agents",
    "agents_symlinks_to_claude",
    "both_link_to_same_file",
    "claude_imports_agents",
    "identical",
    "claude_contains_agents",
)
 
 
def relation_for(c: dict, a: dict) -> str:
    c_chain = [p.lower() for p in c["chain"]]
    a_chain = [p.lower() for p in a["chain"]]
    if c["link"] and a["path"].lower() in c_chain:
        return "claude_symlinks_to_agents"
    if a["link"] and c["path"].lower() in a_chain:
        return "agents_symlinks_to_claude"
    if c["link"] and a["link"] and c_chain and a_chain and c_chain[-1] == a_chain[-1]:
        return "both_link_to_same_file"
    ct, at = c["text"], a["text"]
    if ct is not None and IMPORT_AGENTS.search(split_markdown(ct)[2]):
        return "claude_imports_agents"
    if ct is not None and at is not None:
        nc, na = norm(ct), norm(at)
        if nc == na:
            return "identical"
        if len(na) >= 200 and na in nc:
            return "claude_contains_agents"
    if ct is not None and len(ct.encode("utf-8")) < 600 and MENTIONS_AGENTS.search(ct):
        return "claude_points_to_agents"
    return "separate"
 
 
def strength(relation: str) -> int:
    return 2 if relation in LOADS_AGENTS else 1 if relation == "claude_points_to_agents" else 0

What we measured in each file

  • Length. Bytes, lines, and words, where a word is a run of characters between spaces.
  • Headings and topics. Every Markdown heading outside code. Headings below the title (level 2 and deeper) are sorted into thirteen topics by keyword; one heading can count toward several topics, and a file counts toward a topic if any of its headings does.
  • Commands. A test, build, or lint command named inside a code block or inline code, so prose that only mentions testing does not count.
  • Security. Whether the prose outside code mentions security, secrets, credentials, API keys, tokens, passwords or .env files.
  • Secrets rule. Whether one sentence of the prose holds a prohibition (never, do not, must not, avoid, no), a verb that follows it (commit, log, print, expose, share, hard-code, push, include, store and similar), and a named secret. A bare "token" does not count, because in these files it is as often a language-model token or a design token as a credential.
  • Imperative lines. Lines of prose with never, always, must, do not, avoid, should not, make sure or ensure.
  • Agent names. Whether the prose names a coding agent (Claude, Codex, Cursor, Copilot, Gemini, Windsurf, Cline, Aider, Jules, Junie, Amp, Devin or Kiro).
  • Template signatures. An AGENTS.md whose title is exactly "Repository Guidelines", which Codex's /init prompt asks for, and a CLAUDE.md containing the sentence "This file provides guidance to Claude Code", a common opening we could not trace to a published template.
  • Mostly Chinese, Japanese or Korean text. More than 30% of the letters in those scripts, where splitting on spaces undercounts words. 69 AGENTS.md files qualify; without them, the median AGENTS.md is 840 words instead of 819.
  • Limits. Codex reads 32 KiB of project instructions by default and truncates a file past that point; its default is project_doc_max_bytes = 32768. Claude Code's documentation suggests under 200 lines per CLAUDE.md.

The patterns, exactly as the analysis script applies them:

FENCE = re.compile(r"^\s*(```|~~~)")
HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$")
INLINE_CODE = re.compile(r"`([^`\n]+)`")
 
# Heading categories. A level-2-or-deeper heading counts toward every
# category whose pattern it matches; the level-1 heading is the document's
# title and is not classified.
SECTIONS: list[tuple[str, str]] = [
    ("Commands", r"\b(commands?|scripts?|common tasks|tasks|makefile|how to run|running)\b"),
    ("Testing", r"\b(tests?|testing|verif(y|ication)|validation)\b"),
    ("Build", r"\b(build|building|compile|compilation)\b"),
    ("Setup", r"\b(setup|set up|install|installation|getting started|prerequisites|environment|requirements|quick ?start)\b"),
    ("Code style and conventions", r"\b(style|conventions?|standards?|formatting|lint|linting|naming|best practices|code quality|patterns?)\b"),
    ("Project structure", r"\b(structure|layout|architecture|organi[sz]ation|director(y|ies)|folders?|codebase|modules?|components?|map)\b"),
    ("Project overview", r"\b(overview|about|introduction|summary|purpose|context|background|(tech|technology|technical) stack)\b"),
    ("Git and pull requests", r"\b(git|commits?|pull requests?|prs?|branch(es|ing)?|reviews?|contributing|contributions?)\b"),
    ("Security", r"\b(security|secrets?|credentials?|safety|permissions?|sensitive)\b"),
    ("Documentation", r"\b(documentation|docs|comments|changelogs?)\b"),
    ("Dependencies", r"\b(dependenc(y|ies)|packages?|libraries)\b"),
    ("Troubleshooting", r"\b(troubleshooting|debug(ging)?|common (issues|mistakes|pitfalls)|gotchas?|pitfalls?|known issues|caveats)\b"),
    ("CI and deployment", r"\b(ci|continuous integration|deploy(ing|ment)?|releases?|releasing|publishing)\b"),
]
SECTION_RES = [(name, re.compile(rx, re.I)) for name, rx in SECTIONS]
 
# Commands, matched inside fenced code blocks and inline code spans only, so
# prose that merely mentions testing does not count as a command.
# A package-manager script: "npm test", "pnpm run test:unit", "yarn --cwd web test".
PM = r"\b(?:npm|pnpm|yarn|bun)\b[^\n]*?\s(?:run\s+)?"
TEST_CMD = re.compile(
    PM + r"test(?=[:\s]|$)"
    r"|\bpytest\b|\bgo\s+test\b|\bcargo\s+(?:test|nextest)\b|\bmvnw?\b[^\n]*\btest\b|\bgradlew?\b[^\n]*\btest\b"
    r"|\bmake\s+\S*(?:test|check)\S*|\bdotnet\s+test\b|\brspec\b|\bjest\b|\bvitest\b|\bphpunit\b|\bpest\b|\bctest\b"
    r"|\btox\b|\bnox\b|\bswift\s+test\b|\bmix\s+test\b|\bbazel\s+test\b|\bplaywright\s+test\b|\bdeno\s+test\b"
    r"|\bjust\s+test\b|\bcomposer\s+(?:run-script\s+|run\s+)?test\b|\brake\s+(?:test|spec)\b|\brails\s+test\b"
    r"|\bsbt\b[^\n]*\btest\b|\blein\s+test\b|\b(?:flutter|dart)\s+test\b|\bxcodebuild\b[^\n]*\btest\b"
    r"|\bmeson\s+test\b|\b(?:nx|turbo)\s+(?:run\s+)?test\b|\bzig\s+build\s+test\b|\bR\s+CMD\s+check\b"
    r"|\./run[-_]?tests?\b|\b(?:scripts?|bin|hack|tools)/[\w./-]*test[\w.-]*",
    re.I,
)
BUILD_CMD = re.compile(
    PM + r"build(?=[:\s]|$)"
    r"|\bcargo\s+build\b|\bgo\s+build\b|\bgradlew?\b[^\n]*\b(?:build|assemble)\b"
    r"|\bmvnw?\b[^\n]*\b(?:package|install|compile|verify)\b|\bdotnet\s+build\b|\bbazel\s+build\b|\bcmake\b"
    r"|\bmake\b|\bnext\s+build\b|\bvite\s+build\b|\bswift\s+build\b|\bjust\s+build\b|\bmeson\b|\bninja\b"
    r"|\b(?:flutter|docker)\s+build\b|\bxcodebuild\b|\bsbt\b[^\n]*\b(?:compile|package|assembly)\b"
    r"|\b(?:nx|turbo)\s+(?:run\s+)?build\b|\bzig\s+build\b|\bdart\s+compile\b",
    re.I,
)
LINT_CMD = re.compile(
    PM + r"(?:lint|format|fmt|typecheck|type-check|check-types)(?=[:\s]|$)"
    r"|\beslint\b|\bprettier\b|\bruff\b|\bblack\b|\bisort\b|\bflake8\b|\bpylint\b|\bmypy\b|\bpyright\b"
    r"|\bgolangci-lint\b|\bgofmt\b|\bgo\s+(?:vet|fmt)\b|\bstaticcheck\b|\bclippy\b|\brustfmt\b|\bcargo\s+fmt\b"
    r"|\bbiome\b|\bpre-commit\b|\bstylelint\b|\bshellcheck\b|\bktlint\b|\bdetekt\b|\bspotless\b|\bcheckstyle\b"
    r"|\bdotnet\s+format\b|\brubocop\b|\bstandardrb\b|\bphpstan\b|\bpsalm\b|\bphpcs\b|\bswiftlint\b|\bswiftformat\b"
    r"|\bclang-(?:format|tidy)\b|\bhadolint\b|\bmarkdownlint\b|\byamllint\b|\bdeno\s+(?:lint|fmt)\b|\btsc\b",
    re.I,
)
# Prose (outside code).
SECURITY = re.compile(
    r"\b(?:secrets?|credentials?|api[ _-]?keys?|access tokens?|auth tokens?|passwords?|private keys?|"
    r"security|vulnerabilit(?:y|ies)|sensitive data|pii|personal data)\b|\.env\b",
    re.I,
)
# A rule against leaking secrets: in one sentence, a prohibition followed by
# a verb, and a named secret. A bare "token" is not enough: in these files it
# is as often a language-model token or a design token as a credential.
SECRET_RULE = re.compile(
    r"(?=.*\b(?:never|do not|don't|must not|avoid|no)\b.*?"
    r"\b(?:commit(?:s|ted|ting)?|log(?:s|ged|ging)?|print(?:s|ed|ing)?|expos(?:e|es|ed|ing)|"
    r"shar(?:e|es|ed|ing)|hard-?cod(?:e|es|ed|ing)|check(?:s|ed|ing)? in|push(?:es|ed|ing)?|"
    r"leak(?:s|ed|ing)?|past(?:e|es|ed|ing)|put(?:s|ting)?|pass(?:es|ed|ing)?|includ(?:e|es|ed|ing)|"
    r"stor(?:e|es|ed|ing)|contain(?:s|ed|ing)?|add(?:s|ed|ing)?)\b)"
    r"(?=.*(?:\b(?:secrets?|api[ _-]?keys?|credentials?|passwords?|private keys?|keystores?|"
    r"(?:access|auth|bearer|api|oauth|refresh|session|secret|github|npm|jwt|bot|deploy|client|signing|"
    r"personal access)[ _-]?tokens?)\b|\.env\b))",
    re.I,
)
SENTENCE_END = re.compile(r"(?<=[.!?;])\s+")
RULE = re.compile(r"\b(?:never|always|must|do not|don't|avoid|should not|shouldn't|make sure|ensure)\b", re.I)
AGENT_NAMES = re.compile(
    r"\b(?:claude|codex|cursor|copilot|gemini|windsurf|cline|aider|jules|junie|amp|devin|kiro)\b", re.I
)
# A Claude Code import of AGENTS.md: "@AGENTS.md", "@./AGENTS.md", or from
# .claude/CLAUDE.md "@../AGENTS.md". Searched in prose only, because Claude
# Code's "import parsing skips Markdown code spans and fenced code blocks".
IMPORT_AGENTS = re.compile(r"(?<![\w@/.])@(?:\.{1,2}/)*AGENTS\.md\b", re.I)
MENTIONS_AGENTS = re.compile(r"agents\.md", re.I)
# Words are counted by splitting on whitespace, which undercounts Chinese,
# Japanese and Korean. A file whose letters are more than 30% CJK is flagged
# so the length figures can be checked without it.
CJK = re.compile(r"[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af]")
 
# Template signatures. Codex CLI's /init prompt (openai/codex,
# codex-rs/tui/assets/prompt_for_init_command.md) tells the model to 'Title
# the document "Repository Guidelines"' and says "200-400 words is optimal".
CODEX_INIT_TITLE = "repository guidelines"
# A boilerplate first sentence many CLAUDE.md files share. Counted as text
# only: we found no official source for where it comes from.
CLAUDE_BOILERPLATE = re.compile(r"this file provides guidance to claude code", re.I)

Checking the rules by hand

Pattern rules misread some files, so we read samples of what they flagged.

  • Secrets rule. Of a random sample of 40 AGENTS.md files the rule flagged, 38 held a genuine rule about handling secrets. An earlier draft, which matched whole lines and accepted any "token", scored 31 of 40, mostly by misreading design tokens and language-model tokens, and was tightened before the figures were produced. The rule finds explicit prohibitions only, so it undercounts files that address secrets in other words.
  • Commands. Of 15 files flagged as naming a test command, 14 clearly did; the other names its test runner only as a package. Of 12 files flagged as naming no command, none turned out to name one.
  • How the files relate. We read 36 repositories across the eight relations between the two files and found none misclassified against the rules above.

Edit history

For each root AGENTS.md and CLAUDE.md, and each .claude/CLAUDE.md, we counted the commits on the default branch that touched the file's path and fetched the dates of up to 100 of them. A file's first commit is known when all its commits were fetched. GitHub's path history does not follow renames, so a file renamed from another name dates from the rename. Edit counts leave out symlinks, whose history is the commit that created the link. The quarter table in the report counts files that still exist, so files later deleted are missing from it.

The dataset

One row per repository, 7,370 rows, metrics only. The files' text is not published: it belongs to each repository. Empty cells mean the file is absent.

ColumnMeaning
repoThe repository, as owner/name.
starsStars when the population was listed.
languageGitHub's primary language, if any.
pushed_atDate of the last push, UTC.
agents_md, claude_md1 if the repository has the file, as defined above; else 0.
gemini_md, agent_md, copilot_instructions, cursor_rules, windsurf_rules, cline_rules, junie_guidelines1 if the repository has that kind of instruction file; else 0.
any_instruction_file1 if the repository has any of them.
claude_md_locationroot, .claude or both.
claude_md_relationHow CLAUDE.md relates to AGENTS.md: claude_symlinks_to_agents, agents_symlinks_to_claude, both_link_to_same_file, claude_imports_agents, identical, claude_contains_agents, claude_points_to_agents, separate, or claude_only when there is no AGENTS.md.
claude_md_mentions_agents1 if a CLAUDE.md mentions AGENTS.md.
*_case_variant1 if the file's name is not in the standard case.
*_symlink1 if the file is a symlink; its metrics describe the file it points at.
*_bytes, *_lines, *_wordsLength.
*_headingsNumber of headings, including the title.
*_rulesNumber of imperative lines.
*_sectionsTopics of the headings, separated by semicolons.
*_test_cmd, *_build_cmd, *_lint_cmd1 if the file names that kind of command.
*_security, *_secret_rule1 if the file mentions security, or states a secrets rule.
*_names_agents1 if the prose names a coding agent.
*_cjk_heavy1 if the text is mostly Chinese, Japanese or Korean.
agents_md_codex_title, claude_md_claude_boilerplate1 if the file carries the template signature.
*_commitsCommits that touched the file.
*_first_commitDate of the first, when every commit was fetched.

The * columns exist once for agents_md and once for claude_md.

Re-running the study

Both scripts are plain Python 3.10 or later, with no dependencies beyond the standard library, and need a GitHub token (GITHUB_TOKEN, or the GitHub CLI's) for read-only access to public data.

python collect.py --out raw --snapshot 2026-09-27
python analyze.py --raw raw --csv data.csv --ts summary.ts

collect.py lists the population and reads the files, resuming if interrupted; analyze.py turns the raw collection into the dataset and the summary this report reads its figures from. A new run describes the repositories as they are on the day you run it, not as they were on 27 September 2026. We do not publish the raw collection, because it contains each repository's files.

Download the scripts: collect.py and analyze.py.

Dataset

AGENTS.md and CLAUDE.md census 2026: per-repository metrics

License
CC BY 4.0
Version
1.0
Published
SHA-256
1f4b3228fe0fd7a4a84e93cbd15568834de8875f707d14e498de64707952ea32