CLAUDE LABJP
CODE — Claude Code has moved on to v2.1.267. It was v2.1.263 yesterday, so four releases landed in the space of a single dayEFFORT — A new maxEffortLevel setting caps the effort level across every provider, Bedrock, Vertex and Foundry included. People can still choose something lowerCACHE — The largest part of this release is not a feature at all. More than a dozen fixes address cases where prompt cache reuse quietly brokeRESUME — Resuming a session or switching models with /model could rewrite the tool definitions, and the only visible symptom was a bill that crept upwardGATEWAY — v2.1.266 undoes a regression. Setups carrying CLAUDE_CODE_USE_GATEWAY were failing every request. The fix is the upgrade itself, not a config changePLUGIN — --plugin-dir now accepts a folder of plugins, and a path containing a backslash can no longer slip past the containment check on macOS or LinuxCODE — Claude Code has moved on to v2.1.267. It was v2.1.263 yesterday, so four releases landed in the space of a single dayEFFORT — A new maxEffortLevel setting caps the effort level across every provider, Bedrock, Vertex and Foundry included. People can still choose something lowerCACHE — The largest part of this release is not a feature at all. More than a dozen fixes address cases where prompt cache reuse quietly brokeRESUME — Resuming a session or switching models with /model could rewrite the tool definitions, and the only visible symptom was a bill that crept upwardGATEWAY — v2.1.266 undoes a regression. Setups carrying CLAUDE_CODE_USE_GATEWAY were failing every request. The fix is the upgrade itself, not a config changePLUGIN — --plugin-dir now accepts a folder of plugins, and a path containing a backslash can no longer slip past the containment check on macOS or Linux
Articles/Claude Code
Claude Code/2026-09-10Intermediate

One plugin.json turns a shelf of skills into something you can switch off as a group

When I sorted my skills by what work they belong to, the set I open almost daily turned out to be 5 percent of the total. Here is how I dropped a .claude-plugin/plugin.json into each group so I could switch a whole shelf off, plus the two loading rules I tripped over on the way.

Claude Code250Agent Skills4Plugins3Context DesignIndie Development11

Premium Article

On a rainy afternoon I opened my skills folder and started colour-coding the directories by the work they belong to. SEO research here, frontend implementation there, site operations over on the side. Partway through the sorting I noticed that the shortest column was the one I open most days.

I wrote up the counting itself in I counted 42 skills on my shelf, so I won't repeat it here. What I want to share this time is what comes after the counting.

For a while I assumed that bundling would make things lighter. It didn't. The per-turn weight didn't drop by a single character. I'm still glad I did it — what dropped was the number of moves, not the weight.

Counting by job reveals the ratio

The counting method is the same as before: add up the characters in each description field. The difference is that this time I group before I add.

# group_skills.py — count a skills folder, grouped by the work each skill serves
import os
import re
import sys
 
BASE = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser("~/.claude/skills")
 
GROUPS = {
    "seo":       ["ai-seo", "seo-audit", "programmatic-seo", "content-research-writer"],
    "web-front": ["vercel-optimize", "vercel-react-best-practices", "webapp-testing"],
    "design":    ["frontend-design", "implement-design", "web-design-guidelines"],
    "site-ops":  ["claudelab-site", "gemilab-site"],
}
 
def description_length(folder: str) -> int:
    path = os.path.join(BASE, folder, "SKILL.md")
    if not os.path.isfile(path):
        return 0
    text = open(path, encoding="utf-8", errors="replace").read()
    front = re.search(r"^---\n(.*?)\n---", text, re.S | re.M)
    if not front:
        return 0  # no frontmatter means no description to pay for
    field = re.search(r"^description:\s*(.*?)(?=^\w+:|\Z)", front.group(1), re.S | re.M)
    if not field:
        return 0
    return len(" ".join(field.group(1).split()))
 
lengths = {d: description_length(d) for d in sorted(os.listdir(BASE))
           if os.path.isdir(os.path.join(BASE, d))}
total = sum(lengths.values()) or 1
assigned = set()
 
print(f"{'group':<12}{'n':>4}{'chars':>8}{'share':>8}")
for name, members in GROUPS.items():
    present = [m for m in members if m in lengths]
    assigned.update(present)
    chars = sum(lengths[m] for m in present)
    print(f"{name:<12}{len(present):>4}{chars:>8}{chars / total * 100:>7.1f}%")
 
rest = [k for k in lengths if k not in assigned]
rest_chars = sum(lengths[k] for k in rest)
print(f"{'(rest)':<12}{len(rest):>4}{rest_chars:>8}{rest_chars / total * 100:>7.1f}%")
print(f"total {len(lengths)} folders / {total} chars")

Both the os.path.isfile guard and the 0 return for missing frontmatter earned their place. My shelf still holds a few folders whose contents are nothing but a forwarding note, and the first version of this script raised on them.

Here is what it printed for me. The grouping follows how my own week is divided, so treat it as one example rather than a template.

GroupSkillsDescription charsShare of total
SEO research and audits52,44220.7%
Frontend implementation62,37020.1%
Design and imagery92,41620.5%
Site operations46235.3%
Development workflow71,1459.7%
Everything else112,77423.6%
Total4211,770100%

That table stopped me for a minute. The site-operations set, which I open nearly every day, accounts for 5.3% of the description text. The other 94.7% describes work I am not doing today. That may well be the whole of what "the shelf keeps growing" actually feels like.

Character counts are only a yardstick for comparing your own shelf against itself. When you need real numbers, claude plugin details <name> estimates tokens through the count_tokens API for your active model.

A single .claude-plugin/plugin.json changes what a folder means

A skills directory can hold three different kinds of thing under the same tree.

What you haveWhat it loads as
<skills-dir>/foo/SKILL.md with no manifestA plain skill named foo
<skills-dir>/foo/.claude-plugin/plugin.jsonA plugin foo@skills-dir, which can bundle skills, agents, hooks and more
<plugin>/skills/bar/SKILL.mdA skill bar packaged inside a plugin

There are two places it can load from. Put it in ~/.claude/skills/ for personal scope, where it loads in every project. Put it in <cwd>/.claude/skills/ for project scope, where it loads once you have accepted the workspace trust dialog for that folder.

claude plugin init writes the scaffold for you.

# scaffold at ~/.claude/skills/site-ops/ with skills and hooks folders
claude plugin init site-ops --with skills hooks
 
# rewrite an existing scaffold
claude plugin init site-ops --force

The manifest can stay small. It is optional altogether, and name is the only required field.

{
  "name": "site-ops",
  "displayName": "Site Ops",
  "version": "0.1.0",
  "description": "Article updates and deploy chores for four sites",
  "author": { "name": "Masaki Hirokawa" },
  "skills": "./skills/",
  "hooks": "./hooks/hooks.json"
}

Moving flat folders in gives you a tree like this.

~/.claude/skills/
├── site-ops/
│   ├── .claude-plugin/
│   │   └── plugin.json
│   ├── skills/
│   │   ├── claudelab-site/SKILL.md
│   │   ├── gemilab-site/SKILL.md
│   │   ├── antigravitylab-site/SKILL.md
│   │   └── rorklab-site/SKILL.md
│   └── hooks/
│       └── hooks.json
└── seo-kit/
    ├── .claude-plugin/
    │   └── plugin.json
    └── skills/
        ├── seo-audit/SKILL.md
        └── programmatic-seo/SKILL.md

I keep the migration itself to a fixed four steps.

  1. Note the folder names you're moving, then run claude plugin init <name> --with skills to write the scaffold
  2. Move the original folders under <name>/skills/ as they are, leaving each SKILL.md untouched
  3. Run claude plugin validate ~/.claude/skills/<name> to catch any file whose frontmatter no longer parses
  4. Restart Claude Code and record the Always-on figure from claude plugin details <name>

validate in step 3 exits 0 when it passes, 1 when it fails, and 2 when the validation run itself fails, such as when the path can't be read. I keep the figure from step 4 next to the pre-migration total in a note of my own.

Don't put commands/, agents/, or skills/ inside .claude-plugin/. Only plugin.json belongs there; everything else sits one level up. I got this backwards once and built a plugin that loaded with no components at all.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will be able to regroup a flat skills folder into units you can switch off together, one job at a time
You will be able to measure the per-turn weight of your own skill descriptions broken down by the work they serve, and decide from that
You will be able to avoid two quiet failures before you hit them: a plugin at the repository root that never loads, and a hook you edited that keeps running the old definition
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Claude Code2026-08-22
Switching to headersHelper in Claude Code broke auth for project-scoped catalogs only
Moving a private plugin catalog to headersHelper worked at user scope and failed under the project directory. The cause was credential non-inheritance. Here are two working helpers, measured execution costs, and what unattended runs need.
Claude Code2026-09-06
I counted 42 skills on my shelf, and four of them were just a note saying they had moved
Claude Code v2.1.261 added /skill-doctor. Before running it I counted my own skill shelf from the files, found four tombstones, and learned that the obvious awk one-liner for spotting duplicate plugin names quietly reports skills that are not duplicates at all.
Claude Code2026-09-03
Aligning Log Timezones at Display Time, or Fixing Them at Write Time
AdMob, the store reports, and my own logs each ended the day at a different moment. Here is how I went back and forth between display-side and write-side timezone handling, and what I settled on.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links