The more skills an agent has, the better it should work. That is the intuition. It is wrong. Past thirty or so skills or tools, adding one more capability often degrades the agent instead of improving it. And the problem is not the one you would expect: it is not the context bloating up, it is the routing breaking down.
I have about fifty skills installed in my Claude Code setup. This article explains why that is already in the red zone, what the measurements say, and how to take back control.
The paradox, measured
A 2026 study, More Skills, Worse Agents?, ran the experiment cleanly: start from a set of skills that are useful for the task (the oracle), then drown the agent under an ever-larger library, and measure the pass rate.
| Library size | Pass-rate points lost |
|---|---|
| Useful skills only (baseline) | 0 |
| 52 skills | -8 |
| 102 skills | -14 |
| 202 skills | -21 |
The drop is monotonic and it is not marginal: 21 pass-rate points between a well-equipped agent and the same agent drowned under 202 skills, averaged across two models. The know-how is identical, the necessary tools are still there. Only the noise changed.
An even worse signal: the fraction of runs where the agent invokes no skill at all and does the work by hand rises from 12% (useful skill set) to 38.5% (202 skills). The agent does not just pick the wrong skill, it eventually gives up looking for one.
The three ways one skill too many degrades the agent
Context overhead. Each skill adds its name and description to the startup prompt. At 200 skills, that is real volume, and a longer prompt degrades inference. Real effect but small: about one third of the drop, and statistically indistinguishable from zero in the study. It is the obvious suspect, and it is the wrong one.
Shadowing. This is the real culprit. When two skills have similar descriptions, the wrong one can "mask" the right one because its description happens to match the query slightly better. The agent chooses confidently, and it chooses wrong. This effect dominates: up to 68% of the degradation, and the only statistically significant effect. Crucially, it grows linearly with library size. The more look-alike skills you add, the more chances to be wrong you manufacture.
Abandonment. The endgame of shadowing: the agent, unable to decide, picks nothing and does the work with no skill. A third of runs at 202 skills. This is the most insidious one because it is invisible: in the logs, "no skill invoked" looks like a case where no skill was relevant, not like a routing failure.
The real problem: the router sees 8% of the signal
Why does routing get it so wrong? Because the decision is made on the wrong information.
Claude Code loads skills in three levels, a mechanism called progressive disclosure:
The router only sees level 1, the name and the description, to decide which skill to load. Yet the SkillRouter paper measured, through attention analysis on a cross-encoder, that 91.7% of the routing signal lives in the skill body, the level 2 content. The name and description, the very things the decision is made on, carry only a fraction of the signal.
The proof is in the ablation: removing the body drops routing quality by 29 to 44 points depending on the method. Distilling the body into better descriptions recovers part of the signal, but never all of it. You are asking the router to pick among 200 candidates with 8% of the relevant information. Shadowing is not a bug, it is the mathematical consequence of this design.
The same paper shows that a retrieve-and-rerank pipeline reading the full body reaches 74% Hit@1 on a library of ~80,000 skills, with a model 13 times smaller than the naive alternative. The signal exists. You just have to look at it.
Claude Code's silent truncation
There is a second trap, specific to Claude Code. Skill descriptions go through a character budget, calibrated at around 1% of the context window. When that budget overflows, Claude Code truncates.
The consequences are nasty:
- Truncation cuts the end of the description, often where the trigger keywords live. A skill that matched "open a merge request" no longer matches anything if the sentence is cut before it.
- The least-invoked skills are truncated first. Your rare skills become unreachable, which makes them even rarer.
- Installing skill N+1 can break the routing of skill N. Nothing in skill N changed, but it lost room in the shared budget.
In other words, each added skill is not neutral for the others. You do not only pay context overhead, you redistribute a finite budget between descriptions fighting for the same space. And a public study notes that 26.4% of public skills have no usable routing description at all, which guarantees shadowing from the start.
What operators at scale are doing
The industry's answer converges: stop exposing everything at once, and route intelligently.
| Player | Action | Result |
|---|---|---|
| GitHub Copilot (Nov. 2025) | 40 default tools cut to 13, the rest in virtual groups loaded on demand | +2 to +5 success points, -400 ms latency |
| Anthropic (Nov. 2025) | Tool Search Tool: Claude discovers tools dynamically instead of loading everything | Opus 4.5: tool-use accuracy from 79.5 to 88.1 |
GitHub documented how reducing the default toolset improved SWE-bench and SWE-Lancer on GPT-5 and Sonnet 4.5 alike. Their conclusion fits in one sentence: giving an agent more tools does not make it smarter, just slower.
Anthropic goes the same way with the Tool Search Tool: instead of loading all 200 tool definitions into the context, Claude fetches the relevant one on demand. It is the same principle as the MCP RTK proxy I wrote about here: do not pay in tokens and confusion for what you do not use.
Concrete countermeasures
| Approach | Effort | Evidence |
|---|---|---|
| Reduce the number of active skills | Low | GitHub: -27 tools = +2 to +5 points |
| Move to on-demand (search-based) routing | Medium | Anthropic: +8.6 points on Opus 4.5 |
| Route on the full body (encoder + re-ranker) | High | SkillRouter: 74% top-1 on 80k skills |
In practice, in my Claude Code config, three actions give the best effort-to-payoff ratio:
Audit the descriptions. Check that no critical skill has its description truncated, and that trigger keywords are at the start, not the end. A description that opens with its triggers survives truncation.
Consolidate skills with close triggers. I had qa-swarm variants per project (one per repo), with nearly identical descriptions. That is a textbook shadowing case: four skills fighting over the same query. Merging them into one parameterized skill removes four chances to misroute. It is exactly the work I described in my article on effective skills: a description must say when to fire, not just what the skill does.
Measure, do not guess. Compare the correct-match rate with the full set against a set reduced to the 15 most-used skills. If the reduced set routes better, you have your answer: half your skills cost more than they return.
The threshold, in practice
Degradation becomes measurable past ~30-50 skills. Below that, shadowing exists but stays absorbed by the model's margin. Above it, each addition is paid twice: once in tokens, once in chances to misroute.
The rule I apply now: a skill enters my config only if its description can trigger nothing but itself. If I can imagine a query where it would compete with an existing skill, I either merge or rewrite both descriptions to make them disjoint. One more skill is never free. It is a bet on the router, and the router sees only 8% of the picture.
Top comments (2)
The truncation ordering also masks a separate problem where a skill that gets clipped does not simply fail to fire. In our logs, an incomplete description frequently acts as a wildcard, pulling in unrelated generic tasks because the remaining head of the prompt was too broad. Treating unused skills as zero-cost is definitely where most setups get caught.
Your line about the least-invoked skills being truncated first is the one I recognised. We ran /skill-doctor on our own setup and it listed 40 skills at about 3,060 tokens a turn, of which the 16 plugin skills we had never once invoked accounted for about 1,470. The report never flagged those, because it flags what you can switch off one by one and those live inside a plugin. Of the three it did flag as never invoked, one was a day old, so a zero there can mean new as easily as dead. Have you run your own correct-match comparison against a reduced set yet, and did the never-invoked ones move it at all?