DEV Community

Eliran Kotzero
Eliran Kotzero

Posted on Originally published at kitquarry.com

Too many agent skills? What they cost in context and how to prune

Originally published on KitQuarry. Written by Eliran Kotzero for KitQuarry.

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 /doctor for an estimate of the listing's context cost and its biggest contributors. The Skills row in /context shows 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: /skills lists every skill and groups synced ones under claude.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 /skills to 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")
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode
# ~/.codex/config.toml
[[skills.config]]
path = "/home/you/.agents/skills/old-helper/SKILL.md"
enabled = false
Enter fullscreen mode Exit fullscreen mode

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
---
Enter fullscreen mode Exit fullscreen mode

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.md or 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.
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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/skills below the launch folder at startup; they load the first time Claude reads or edits a file in that subdirectory. Cursor scopes a nested .cursor/skills or .agents/skills to files in its directory. Codex scans from the launch folder up to the repo root, so a skill in services/payments/.agents/skills is picked up when you start Codex in that service.
  • paths frontmatter (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/**"
---
Enter fullscreen mode Exit fullscreen mode

A ten-minute cleanup

  1. Run /skill-doctor in Claude Code (or the script above) and list skills you haven't used in a month.
  2. Delete or turn off each one. For plugin skills, disable the plugin.
  3. Add disable-model-invocation: true to every skill with side effects.
  4. Merge skills that answer the same requests.
  5. Move repo-specific personal skills into their repos, and package-specific ones into nested folders.
  6. Rewrite each remaining description: what it does, then "Use when..." with real phrasings, under a few hundred characters.
  7. Check /context or /doctor again, 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.

Sources

Official docs, checked 2026-10-30.


If you want a starting point, KitQuarry's free sampler is on GitHub: ai-coding-rules-sampler. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)

Top comments (0)