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.mdandCLAUDE.mdat the root, and of.claude/CLAUDE.md; - whether these exist:
GEMINI.md,AGENT.md,.github/copilot-instructions.md,.cursorrulesor a.cursor/rulesdirectory,.windsurfrulesor a.windsurf/rulesdirectory,.clinerules, and.junie/guidelines.md; - the commits that touched
AGENTS.mdandCLAUDE.mdon 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.mdin any letter case. 14 use another case, such asagents.md; Codex looks for the exact nameAGENTS.md, so on a case-sensitive file system it would not find them. - Has a CLAUDE.md.
CLAUDE.mdat 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.mdis personal and usually not committed, so we did not look for it. - Symlinks. When
AGENTS.mdorCLAUDE.mdis a symlink, we followed it, through further links if necessary, to the file it points at and measured that file. 88AGENTS.mdfiles and 305CLAUDE.mdfiles 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:
CLAUDE.mdis a symlink that resolves toAGENTS.md.AGENTS.mdis a symlink that resolves toCLAUDE.md.- Both are symlinks that resolve to the same third file.
CLAUDE.mdcontains an import ofAGENTS.md(@AGENTS.md,@./AGENTS.md, or from.claude/,@../AGENTS.md) outside code spans and code blocks.- The two files have the same text once runs of whitespace are collapsed.
CLAUDE.mdcontains the whole text ofAGENTS.md, which must be at least 200 characters.CLAUDE.mdis under 600 bytes and mentionsAGENTS.md: a pointer that tells Claude to read it.- 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 0What 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
.envfiles. - 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.mdwhose title is exactly "Repository Guidelines", which Codex's/initprompt asks for, and aCLAUDE.mdcontaining 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.mdfiles qualify; without them, the medianAGENTS.mdis 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 perCLAUDE.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.mdfiles 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.
| Column | Meaning |
|---|---|
repo | The repository, as owner/name. |
stars | Stars when the population was listed. |
language | GitHub's primary language, if any. |
pushed_at | Date of the last push, UTC. |
agents_md, claude_md | 1 if the repository has the file, as defined above; else 0. |
gemini_md, agent_md, copilot_instructions, cursor_rules, windsurf_rules, cline_rules, junie_guidelines | 1 if the repository has that kind of instruction file; else 0. |
any_instruction_file | 1 if the repository has any of them. |
claude_md_location | root, .claude or both. |
claude_md_relation | How 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_agents | 1 if a CLAUDE.md mentions AGENTS.md. |
*_case_variant | 1 if the file's name is not in the standard case. |
*_symlink | 1 if the file is a symlink; its metrics describe the file it points at. |
*_bytes, *_lines, *_words | Length. |
*_headings | Number of headings, including the title. |
*_rules | Number of imperative lines. |
*_sections | Topics of the headings, separated by semicolons. |
*_test_cmd, *_build_cmd, *_lint_cmd | 1 if the file names that kind of command. |
*_security, *_secret_rule | 1 if the file mentions security, or states a secrets rule. |
*_names_agents | 1 if the prose names a coding agent. |
*_cjk_heavy | 1 if the text is mostly Chinese, Japanese or Korean. |
agents_md_codex_title, claude_md_claude_boilerplate | 1 if the file carries the template signature. |
*_commits | Commits that touched the file. |
*_first_commit | Date 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.tscollect.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.