Too many agent skills? What they cost in context and how to prune
In short: an installed skill costs context in two ways. Its name and description sit in a listing the model sees on every turn. Its full SKILL.md enters the conversation once it's used and stays there. The listing has a budget (1% of the context window in Claude Code, 2% in Codex by default), and when it overflows, descriptions get cut and skills stop triggering. Audit what's installed, turn off what you don't use, make side-effect skills manual-only, move repo-specific skills into the repo, and keep every description short with the trigger words first.
How skills load
All three tools follow the Agent Skills standard and use progressive disclosure: the model starts with a short entry per skill and reads the rest only when it picks one.
| Tool | At session start | When a skill is used | Listing budget |
|---|---|---|---|
| Claude Code | Name and description of every skill Claude can invoke | Rendered SKILL.md enters the conversation and stays across turns | Scales at 1% of the context window; each entry capped at 1,536 characters |
| Codex | Name, description and file path | Reads the full SKILL.md | At most 2% of the context window, or 8,000 characters if unknown |
| Cursor | Discovers skills and presents them to Agent, which decides when they're relevant | Loads the skill; resources load on demand | Not documented |
So the skill you never use isn't free. Claude Code's docs put it plainly: every skill in the listing adds to your context on every turn, whether or not Claude ever uses it.
What actually costs context
| Part of a skill | When it costs context |
|---|---|
name + description (+ when_to_use in Claude Code) | Every turn, for every skill the model can invoke on its own |
Body of SKILL.md | From the moment it's invoked. In Claude Code it stays in the conversation on later turns, so every line is a recurring cost |
Supporting files (references/, examples.md) | Only when the agent reads them |
Scripts (scripts/) | Executed, not loaded. Only their output reaches the context |
Manual-only skills (disable-model-invocation: true) | Claude Code leaves the description out of the listing entirely, so nothing until you type /name |
| Skills preloaded into a Claude Code subagent | The full content is injected when that subagent starts |
The listing is where too many skills hurt. In Claude Code every skill name always stays in it, but when the descriptions don't fit the budget, Claude Code drops descriptions, starting with the skills you invoke least. A skill listed by name only loses the keywords Claude matches against, so it stops triggering on its own. Codex works the same way in its own terms: it shortens descriptions first, and with a large skill set it may leave some skills out of the initial list and show a warning.
After auto-compaction, Claude Code re-attaches each invoked skill but keeps only its first 5,000 tokens, within a combined 25,000-token budget filled from the most recently used skill. Invoke many skills in one long session and the older ones can drop out entirely.
How many skills is too many?
None of the three tools publishes a maximum count, and the number that matters isn't the count. It's whether the descriptions you rely on still fit. Signs you're past it:
- Claude Code: run
/doctorfor an estimate of the listing's context cost and its biggest contributors. The Skills row in/contextshows the listing's size after the budget is applied. When the listing exceeds its budget, a warning goes to the debug log (claude --debug). - Codex: a warning that some skills were left out of the initial list.
- Any tool: a skill that used to trigger now only runs when you call it by name.
You can raise the budget (skillListingBudgetFraction, for example 0.02 for 2%, or SLASH_COMMAND_TOOL_CHAR_BUDGET in Claude Code; skills.max_context_tokens, up to 10,000 tokens, in Codex). That trades context for discoverability. Pruning usually gets you more.
Step 1: audit what's installed
Skills come from more places than most people check. Claude Code loads them from enterprise, personal (~/.claude/skills), project (.claude/skills, from the launch folder up to the repo root), nested subfolders, --add-dir directories, plugins (as /plugin-name:skill-name), and skills synced from your claude.ai account (~/.claude/skills/synced/). Codex reads .agents/skills from the launch folder up to the repo root, ~/.agents/skills, /etc/codex/skills and its bundled system skills. Cursor reads .agents/skills, .cursor/skills and their ~/ versions, and for compatibility also .claude/skills, .codex/skills, ~/.claude/skills and ~/.codex/skills.
- Claude Code:
/skillslists every skill and groups synced ones underclaude.ai sync./skill-doctor(v2.1.252 or later) shows what each skill costs and how often it's used, flags skills that have never been invoked, and says where to turn them off. Start with the ones that cost the most. - Codex: run
/skillsto see what's available. - Cursor: open Customize in the sidebar, then Skills.
For a quick look across all three tools' folders, this script lists every SKILL.md visible from the current folder (personal folders, then project folders up to the repo root), sorted by description length, and marks manual-only skills:
#!/usr/bin/env python3
"""List agent skills visible from this folder, with description size and body length."""
import pathlib, re, subprocess
home, cwd = pathlib.Path.home(), pathlib.Path.cwd()
try:
root = pathlib.Path(subprocess.run(["git", "rev-parse", "--show-toplevel"],
capture_output=True, text=True, check=True).stdout.strip())
except Exception:
root = cwd
dirs = [home / d for d in (".claude/skills", ".agents/skills", ".cursor/skills", ".codex/skills")]
p = cwd
while True: # project folders from here up to the repo root
dirs += [p / d for d in (".claude/skills", ".agents/skills", ".cursor/skills", ".codex/skills")]
if p == root or p == p.parent:
break
p = p.parent
rows, seen = [], set()
for d in dirs:
found = (list(d.glob("*/SKILL.md")) + list(d.rglob("SKILL.md"))) if d.is_dir() else []
for f in sorted(found):
if f.resolve() in seen:
continue
seen.add(f.resolve())
text = f.read_text(errors="replace")
m = re.match(r"---\n(.*?)\n---\n?(.*)", text, re.S)
meta, body = (m.group(1), m.group(2)) if m else ("", text)
desc = re.search(r"^description:\s*(.*)$", meta, re.M)
manual = re.search(r"^disable-model-invocation:\s*(true|yes|on|1)\s*$", meta, re.M | re.I)
rows.append((str(d).replace(str(home), "~").replace(str(root), "."), f.parent.name,
len(desc.group(1).strip().strip("\"'")) if desc else 0, body.count("\n") + 1, "manual" if manual else ""))
print(f"{'location':<40} {'skill':<26} {'desc':>5} {'lines':>6}")
for loc, name, dl, lines, flag in sorted(rows, key=lambda r: -r[2]):
print(f"{loc:<40} {name:<26} {dl:>5} {lines:>6} {flag}")
auto = sum(r[2] for r in rows if not r[4])
print(f"\n{len(rows)} skills, {auto} description characters in auto-invocable skills")
Run it with python3 list_skills.py from your project. It reads single-line description: values only, follows symlinked skill folders and counts each target once. It can't see plugin, bundled or enterprise skills, or nested skills below the current folder, so use /skill-doctor for the full Claude Code picture. We tested it on a sample repo; it doesn't call any of the tools.
Step 2: turn off or remove what you don't use
| Where the skill comes from | To remove it | To keep it but hide it |
|---|---|---|
| Claude Code personal or project | Delete ~/.claude/skills/<name>/ or .claude/skills/<name>/ | skillOverrides, or disable-model-invocation: true |
| Claude Code plugin | /plugin uninstall or disable it in /plugin | Not covered by skillOverrides; manage it in /plugin |
| Synced from claude.ai | Turn it off for your claude.ai account (deleting the folder makes the next sync download it again) | Same |
| Claude Code bundled | disableBundledSkills: true | "off" in skillOverrides |
| Codex local | Delete the folder | [[skills.config]] with enabled = false, then restart |
| Cursor | Delete the folder | disable-model-invocation: true |
Claude Code's skillOverrides has four states. In the /skills menu, highlight a skill and press Space to cycle them, then Esc to save to .claude/settings.local.json:
| Value | Listed to Claude | In the / menu |
|---|---|---|
"on" | Name and description | Yes |
"name-only" | Name only | Yes |
"user-invocable-only" | Hidden | Yes |
"off" | Hidden | Hidden |
// .claude/settings.local.json
{
"skillOverrides": {
"old-helper": "off",
"release-notes": "user-invocable-only",
"legacy-context": "name-only"
}
}
# ~/.codex/config.toml
[[skills.config]]
path = "/home/you/.agents/skills/old-helper/SKILL.md"
enabled = false
Step 3: make side-effect skills manual-only
Deploy, commit, release and "send a message" skills shouldn't fire on their own anyway. Marking them manual-only also takes them out of the budget in Claude Code, where the description is no longer in context at all.
---
name: deploy
description: Deploys the current branch to staging or production. Only when the user asks to deploy.
disable-model-invocation: true
---
Cursor reads the same field: the skill is included only when you type /skill-name. In Codex, set allow_implicit_invocation: false under policy in the skill's agents/openai.yaml; explicit $skill invocation still works. The Codex docs don't say whether that also removes it from the initial list.
Step 4: consolidate and slim the rest
- Merge overlapping skills. Three skills for "write tests", "add coverage" and "fix failing tests" compete for the same requests. One skill with a clear description and three sections in its body costs one listing entry.
- Keep each skill to one job. That's the Codex docs' first best practice, and it's what lets a short description be accurate. Merging overlaps and keeping one job per skill aren't in conflict: merge skills that do the same job, split skills that do several.
- Move reference material out of
SKILL.md. Keep the body under 500 lines and put API details or long examples in files it links to. They load only when the agent reads them. - Write the body as instructions, not explanation. In Claude Code an invoked body stays in context across turns, so the docs suggest stating what to do rather than narrating how or why.
- Turn always-loaded procedures into skills. The reverse problem also exists: a multi-step procedure in
CLAUDE.mdor an always-on rule loads every session, while a skill's body loads only when it's used.
Step 5: short, trigger-first descriptions
The description does two jobs: it's what the model matches your request against, and it's the part every skill pays for on every turn. Claude Code caps each entry's combined description and when_to_use at 1,536 characters, and Codex shortens descriptions when the list is crowded. Both docs say the same thing: put the key use case and trigger words first.
description: This skill is a comprehensive helper that can assist with many aspects of testing, including writing new tests, reviewing existing tests, improving coverage, and general quality work across the codebase.
description: Writes and runs unit tests for changed code. Use when the user asks to add tests, raise coverage, or reproduce a bug with a failing test. Not for end-to-end tests.
The second is shorter and names the requests people actually type. For the full pattern and a test loop, see how to write a SKILL.md that actually triggers.
Step 6: scope skills to where they're used
A personal skill loads in every project on your machine. If you use it in one repo, move it there.
- Personal folders (
~/.claude/skills,~/.agents/skills,~/.cursor/skills): only skills you want in every repo. - Project folders (
.claude/skills,.agents/skills): skills for this repo, committed so the team gets them. - Nested folders for one package in a monorepo. Claude Code doesn't load skills in a
.claude/skillsbelow the launch folder at startup; they load the first time Claude reads or edits a file in that subdirectory. Cursor scopes a nested.cursor/skillsor.agents/skillsto files in its directory. Codex scans from the launch folder up to the repo root, so a skill inservices/payments/.agents/skillsis picked up when you start Codex in that service. pathsfrontmatter (Claude Code and Cursor): glob patterns that limit when a skill is used. Claude Code loads it automatically only when working with matching files; Cursor surfaces it only then.
---
name: migration-rules
description: Rules for writing database migrations. Use when creating or editing a migration.
paths:
- "db/migrations/**"
---
A ten-minute cleanup
- Run
/skill-doctorin Claude Code (or the script above) and list skills you haven't used in a month. - Delete or turn off each one. For plugin skills, disable the plugin.
- Add
disable-model-invocation: trueto every skill with side effects. - Merge skills that answer the same requests.
- Move repo-specific personal skills into their repos, and package-specific ones into nested folders.
- Rewrite each remaining description: what it does, then "Use when..." with real phrasings, under a few hundred characters.
- Check
/contextor/doctoragain, then test the skills you kept in a fresh session.
The same thinking applies to instruction files: see why a long CLAUDE.md gets ignored, and for keeping one set of rules across tools, AGENTS.md vs CLAUDE.md. MCP servers have their own per-tool files, covered in one MCP server, three configs.
FAQ
How many Claude Code skills is too many?
None of the three tools publishes a maximum. The limit that matters is the skill listing budget. In Claude Code it scales at 1% of the context window, and when the listing overflows, Claude Code drops descriptions, starting with the skills you invoke least. Once a skill's description is gone, Claude can't match your request to it. Run /doctor for the listing's cost and /skill-doctor to find skills you never use.
Do installed skills use context even if I never use them?
Their listing entry does. Claude Code's docs say every skill in the listing adds to your context on every turn, whether or not Claude uses it. The SKILL.md body loads only when the skill is invoked. A skill with disable-model-invocation: true is left out of the listing, so it costs nothing until you call it.
How do I turn off a skill without deleting it?
In Claude Code, set it to "off" or "name-only" in skillOverrides, or use the /skills menu, which saves to .claude/settings.local.json. Plugin skills ignore skillOverrides, so disable the plugin with /plugin. In Codex, add a [[skills.config]] entry with the SKILL.md path and enabled = false to ~/.codex/config.toml, then restart Codex.
How much of the context window can Codex's skill list use?
By default the initial list uses at most 2% of the model's context window, or 8,000 characters when the window size is unknown. With many skills, Codex shortens descriptions first and may leave some skills out with a warning. skills.max_context_tokens sets a different budget, up to 10,000 tokens.
Should skills be personal or per project?
Keep only skills you use in every repo in ~/.claude/skills or ~/.agents/skills. Put repo-specific skills in that repo's .claude/skills or .agents/skills, and module-specific ones in a nested folder. Claude Code loads nested skills only once it works on files there, and Cursor scopes nested skills to their directory.