[BUG] Session-start skill listing silently truncates most skill descriptions under a size budget

Status Open
Reported on v2.1.219
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 25, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

With many registered skills, the session-start available-skills listing silently drops the description frontmatter for most of them, so those skills arrive name-only. No indicator that anything was omitted, so autonomous description-based skill routing degrades without the user or model knowing.

Setup: project tier 36 skills (symlinked into .claude/skills/, ~17.7k chars total description frontmatter) + personal tier ~30 skills in ~/.claude/skills.

Observed (two independent fresh sessions, no-tools listing test):

  • Only 9 of 36 project skills carry description text; 27 are name-only.
  • The set of 9 is IDENTICAL across sessions (deterministic).
  • Pattern fits a fixed description budget filled greedily in listing (alphabetical) order, skipping entries that no longer fit: a 642-char description at position 5 IS included, a 265-char one at position 13 is NOT, a ~100-char one at position 24 IS (fits the remainder).
  • All skills remain invocable by name; only descriptions are dropped. All 36 SKILL.md files have well-formed single-key description frontmatter (no BOM, no CRLF; block-scalar and inline forms both affected and both sometimes load).

Impact: workspaces relying on descriptions alone silently lose routing for most skills; the budget is undocumented so description authors cannot design for it.

Related, not duplicates: #31505 (closed stale) reported skills beyond ~28 dropped ENTIRELY (Skill() failed with "Unknown skill"); this report is the successor behavior, where all skills load but descriptions are silently truncated instead, possibly the tradeoff introduced when entity-dropping was addressed. #64466 is the desktop Customize > Skills PANEL showing a non-deterministic subset while the engine loads all skills; different layer, opposite determinism.

What Should Happen?

Either all skill descriptions load, or the listing carries an explicit marker that N descriptions were truncated so the model knows to consult another routing source. Documenting the budget would also let skill authors design within it.

Error Messages/Logs

Steps to Reproduce

  1. Register enough skills that total description frontmatter is well above ~4k chars (e.g. 36 skills, ~17.7k chars).
  2. Open a fresh session and ask: "Without using any tools, list the project skills exactly as they appear in your available-skills context, and say for each whether it came with description text or name only."
  3. Repeat in a second fresh session and compare: the same subset carries descriptions both times.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.219 (desktop app, not the terminal CLI); Environment: Claude.app version 1.24012.9

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗

3 Comments

bcherny collaborator · 14 days ago

Thanks for the detailed report. I tried this on the released CLI 2.1.233 (macOS) with a throwaway project holding 36 skills (~21k characters of description frontmatter) and asked the model, tools-off, which skills arrived with description text. Under the default budget on a 1M-context model everything fit; when I forced the listing over budget, most project skills arrived name-only, deterministically across fresh sessions, with no marker in the listing itself — matching what you saw.

This is intended, documented behavior rather than a bug: the skill listing gets a fixed budget (1% of the model's context window by default) and, when it overflows, every skill name is kept but descriptions are dropped starting with the least-used skills (in a fresh session with no usage history that degenerates to listing order, which is why the subset is stable). It is documented here: https://code.claude.com/docs/en/skills#skill-descriptions-are-cut-short. You can raise the budget with the skillListingBudgetFraction setting (or SLASH_COMMAND_TOOL_CHAR_BUDGET), mark low-priority skills "name-only" via skillOverrides, and /doctor estimates the listing cost; --debug logs a warning when the listing overflows.

That said, we agree the silent part is a fair complaint: the model gets no signal that descriptions were dropped, so description-based routing degrades invisibly. We're considering surfacing an explicit truncation marker in the listing and/or a visible in-session notice. Leaving this open as a usability improvement.

🤖 Generated with Claude Code

ralucaoda · 14 days ago

Thanks Boris, appreciate the repro and the pointer. I had missed the listing-budget section in the docs; skillListingBudgetFraction and "name-only" overrides solve my case.

One extra data point that may be useful for the docs: with no usage history the drop order was listing order as you say, but the selection behaved greedily rather than as a simple cutoff. A 642-char description at position 5 was kept, a 265-char one at position 13 was dropped, then a ~100-char one at position 24 was kept because it still fit the remaining budget. So the phrase "starting with the skills you invoke least" under #skill-descriptions-are-cut-short may give a slightly different mental model from what a fresh workspace actually produces.

Glad the truncation marker / in-session notice is on the table. Even just telling the model that "N skill descriptions were omitted due to the listing budget" would restore the missing routing awareness; a user-facing notice could point to /doctor.

Thanks again for digging into it.

DanceNitra · 4 days ago

On the truncation marker: there is a precedent in your own history worth looking at first. @sammcj filed the 2.1.132 warning in #56832 as insufficient, because it hid 61 of 64 behind "+61 more". What it did do is name the sort key and the first three:

64 descriptions dropped (full descriptions kept for most-used skills) (3.1%/1% of context): context-mode:context-mode, xlsx, impeccable:impeccable, +61 more

I have not measured what the current CLI prints, so I am pointing at that string as a shape rather than claiming anything about today's build.

For the in-listing marker I think @ralucaoda's bare count is enough. The model can already see which names arrived without descriptions, so telling it how many is the missing half and nothing more is needed.

The user-facing notice is where a count runs out, and the reason is the rule you already stated:
descriptions go starting with the least-used skills. That makes the dropped set a function of the
usage history rather than of the install, and I went and measured what that costs. Seeding skillUsage into an isolated CLAUDE_CONFIG_DIR, 220 skills with descriptions alternating 900 and 150 characters, request body read rather than the model asked:

no usage history                    41 kept, listing positions 1..42
history inverted against listing    41 kept, listing positions 180..220

So a user who reports "it stopped picking my skill" cannot be answered from their install, because the surviving set moved with their usage and the state that produced it is gone. Naming the dropped skills, as 2.1.132 did, is what makes that answerable after the fact. That ask is already open twice, as #79503's registered-versus-dropped diagnostic and in #84156, so it has a constituency and this is a measurement for it rather than a new request.

One correction to myself, because it changes the rule. A single arm at a 500x count ratio had me about to write that recency does not enter the ranking; it does. Sweeping the ratio between a year-stale half and a fresh one:

1x, 2x, 5x     ->  the fresh half keeps its descriptions
10x, 20x       ->  the stale-but-used half keeps them

The winner flips between 5x and 10x. Ten uses of a year-old skill beat one use of a fresh one; five do not. So recency is a bounded factor rather than an absent one, and 500x was simply too far past the boundary to show it.

@ralucaoda's greedy fill reproduces as well: in the no-history arm position 41 at 900 characters is dropped and position 42 at 150 is kept. Once a usage history exists the survivors are a contiguous prefix of the ranking with nothing skipped.

Boundaries. Claude Code 2.1.246 on win32, one run per arm, deterministic by construction rather than repeated. The three of us are on three surfaces, @ralucaoda 2.1.219 desktop and you 2.1.233 macOS CLI, and only the name-kept behaviour is common to all three; everything above about usage history has this one surface behind it. The counts are synthetic rather than a workspace observed drifting. And a surviving description is counted by a unique 12-character prefix, not by its full text.

Probe and receipt: probes/does_the_listing_drop_by_usage_or_by_position.py in DanceNitra/agora. Zero completions, so the sweep costs nothing to re-run.

Written with AI assistance, and I work on agent memory tooling, so treat the framing as interested and the numbers as re-runnable.