Skip to content

dnh33/toolsearch-for-skills

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

toolsearch-for-skills

Defer Claude Code plugin-skill descriptions out of every-turn context, and search them on demand.

A userland implementation of anthropics/claude-code#45332 ("extend ToolSearch deferral to plugin skills"), which was closed not planned. No binary patching — it uses a documented settings key plus a small hook, so it survives Claude Code's auto-updates and is fully reversible.

Unofficial, community project. Not affiliated with Anthropic. Built and verified against Claude Code 2.1.144 (native build); the settings key and hook contract it relies on are documented in the CLI itself.


The problem

Claude Code injects every installed skill's name + full description into an <available_skills> block on every turn. The cost scales linearly with how many plugins you have and you pay it whether or not any skill is relevant to the task.

Built-in tools got ToolSearch deferral in v2.1.69 (load names, fetch schemas on demand). MCP tools have defer_loading: true. Plugin skills got nothing.

In the author's setup, 270 skills were indexed — roughly 24,000 tokens of descriptions baked into every single prompt. The issue's own example cites ~9,350 tokens for 152 skills.

What this does

It splits the skill listing the way ToolSearch already splits tools — names stay, descriptions go on demand:

  1. Defer the descriptions. Sets the documented skillListingMaxDescChars setting low, so the per-turn block becomes - plugin:skill: … (names only). Skills remain fully invocable — the Skill tool resolves by name from the registry, independently of what's displayed.
  2. Keep them discoverable. A local index (skill-index.json) holds every skill's full description. tss-search "<query>" ranks them with IDF-weighted keyword matching (the same idea as the native ToolSearch matcher) and prints the matches with full descriptions.
  3. Tell the model how. A SessionStart hook injects a ~50-token pointer explaining that descriptions are deferred and how to search.
Stock Claude Code With this
Per-turn skill cost O(n) — every skill's full description O(1) base (names) + O(k) on query
Discovery Always present, always paid for tss-search on demand
Invocation Skill tool by name Skill tool by name (unchanged)
Prompt cache Prefix shifts as skills change More stable prefix

Measured, on this machine

270 skills indexed
full descriptions ≈ 97,000 chars (~24,000 tokens) per turn
names only         ≈  8,000 chars (~2,000 tokens)
deferred           ≈ 89,000 chars (~22,000 tokens) off every turn

Savings scale with the number of enabled skills.

How discovery stays accurate

The index merges three sources, keyed by exact skill name, longest real description wins:

  • Transcripts (authoritative) — Claude Code records the rendered listing into each session transcript as a {type:"skill_listing"} attachment. This is exactly what was loaded for your environment, including bundled anthropic-skills and CLI built-ins that aren't files on disk.
  • DiskSKILL.md frontmatter under plugins/cache and ~/.claude/skills, for full descriptions and anything not in a recent transcript.
  • Prior index — so a full description captured before install is never lost when later rebuilds only see truncated () descriptions.

Install

Requires Node 18+.

git clone https://github.com/dnh33/toolsearch-for-skills.git
cd toolsearch-for-skills

node install.mjs              # cap = 1 (names only); maximum savings
node install.mjs --cap 60     # keep a short inline hint per skill instead
node install.mjs --dry-run    # preview the exact changes, write nothing

The installer:

  • copies bin/ + lib/ to ~/.claude/skill-tools/,
  • builds ~/.claude/skill-tools/skill-index.json,
  • backs up ~/.claude/settings.json, then sets skillListingMaxDescChars and adds one SessionStart hook (existing hooks are preserved; re-running is idempotent).

Open a new Claude Code session for it to take effect.

Searching

node ~/.claude/skill-tools/bin/tss-search.mjs "fill out a pdf form"
node ~/.claude/skill-tools/bin/tss-search.mjs "shopify +checkout" --max 5
node ~/.claude/skill-tools/bin/tss-search.mjs --list        # all indexed names

+term requires a word. Then invoke the chosen skill by its exact name with the Skill tool.

Uninstall

node uninstall.mjs            # removes the setting, the hook, and ~/.claude/skill-tools
node uninstall.mjs --keep-cap # keep skillListingMaxDescChars, remove the rest

A settings.json backup is written before any change. Open a new session to return to full descriptions.

Verification

This was validated against Claude Code 2.1.144 (native build):

  • Truncation is real. A headless session run with skillListingMaxDescChars: 1 produced a transcript whose skill_listing attachment had 108/108 lines as - name: …. The mechanism is the CLI's own skillListingMaxDescChars (settings schema: "Per-skill description character cap in the skill listing sent to Claude (default: 1536)").
  • Full loop, end to end. A real session with the cap and the SessionStart hook active: the hook fired and injected the pointer, descriptions were truncated, the model ran tss-search "deploy coolify" via Bash, then invoked Skill(skill="coolify") — which resolved with no error. Search → invoke works against the live CLI.
  • Invocation is unaffected — the Skill tool validates names against the full registry, not the displayed list.
  • Search ranks correctly across diverse queries (pdf, pptx, xlsx, coolify, brainstorming, accessibility-review, optimize-images, commit-push-pr…).
  • Install/uninstall preserve pre-existing hooks and are idempotent and reversible.

Tests

node --test    # or: npm test

42 zero-dependency tests (Node's built-in runner) cover frontmatter parsing (block scalars, quoting, line-wrapping, field aliases), the IDF/stem/stopword search ranking, the three-source discovery merge (transcript + disk + prior index, including truncation handling), and the full CLI lifecycle — build-index, search, the hook's JSON output, and install/uninstall against throwaway config dirs (dry-run writes nothing, idempotent re-install, existing hooks preserved, malformed settings.json aborts without clobbering, clean reversal).

Limitations

  • skillListingMaxDescChars is global — built-in/bundled skill descriptions are truncated too, not only plugins (the search index covers all of them, so discovery is preserved across the board).
  • Names are still listed each turn (~2K tokens here). Removing names entirely would require patching the signed, auto-updating native binary — explicitly out of scope; this trades the last ~10% for robustness.
  • The index reflects skills seen in recent transcripts + on disk. A brand-new plugin is picked up on the next session (the hook rebuilds when the corpus changes).
  • Over-inclusion across environments. The index is the union of skills seen recently, so it can list a skill that isn't enabled in the current session (e.g. a headless -p run loads fewer skills than interactive, or a different project enables a different set). If the model invokes such a skill the Skill tool returns a recoverable Unknown skill: <name> — it just re-searches or picks another. It never blocks or crashes. Searching from the same kind of session you built the index in keeps names aligned.

How it works (internals)

See docs/DESIGN.md for the full design and the reverse-engineering notes that back every claim (the INq/Swq/csH skill-listing path, the validateInput invocation gate, and why the binary's own isSkillsAsToolsEnabled path is dead code).

License

MIT © dnh33

About

Defer Claude Code plugin skill descriptions out of every-turn context and search them on demand (userland impl of anthropics/claude-code#45332)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors