From 2e6e501fdc986b691c2a910b42c97c4d04093496 Mon Sep 17 00:00:00 2001 From: Eli Grumman Date: Wed, 8 Apr 2026 10:17:52 +0300 Subject: [PATCH] Fix fleet-auditor SkillBloat over-count (fixes #16) SkillBloat was measuring the full SKILL.md file per skill, inflating the reported skill overhead by ~19x and producing false high-severity findings (e.g. $136/mo skill_bloat on a fleet where /context shows the real overhead is ~$7/mo). Claude Code only loads each skill's YAML frontmatter (name + description) into the session at startup. SKILL.md bodies are loaded on demand when the user invokes the skill via the Skill tool. Fix: add `_estimate_skill_frontmatter_tokens()` that parses and measures only the frontmatter block. Falls back to the documented 100-token default if no frontmatter is present. Verified on a 41-skill fleet: - Before: 75,578 tokens total (avg 1,843/skill) - After: 4,011 tokens total (avg 97/skill) - Matches Claude Code's own /context output. --- skills/fleet-auditor/scripts/fleet.py | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/skills/fleet-auditor/scripts/fleet.py b/skills/fleet-auditor/scripts/fleet.py index a571ec6..93e0975 100644 --- a/skills/fleet-auditor/scripts/fleet.py +++ b/skills/fleet-auditor/scripts/fleet.py @@ -43,8 +43,34 @@ init_sqlite_db, migrate_add_columns, estimate_tokens_from_file, + estimate_tokens_from_text, ) + +def _estimate_skill_frontmatter_tokens(skill_md: Path) -> int: + """Estimate tokens of a skill's YAML frontmatter only. + + Claude Code loads only each skill's frontmatter (name + description) + into the session at startup. The SKILL.md body is loaded on demand + when the user invokes the skill via the Skill tool. Measuring the + full file over-counts overhead by ~10-20x. + + Falls back to 100 tokens (the documented average) if frontmatter + cannot be parsed. + """ + try: + text = skill_md.read_text(encoding="utf-8", errors="replace") + except (OSError, PermissionError): + return 100 + if not text.startswith("---"): + return 100 + # Look for the closing --- after the opening one + end_idx = text.find("\n---", 4) + if end_idx == -1: + return 100 + frontmatter = text[: end_idx + 4] + return estimate_tokens_from_text(frontmatter) + # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- @@ -549,6 +575,10 @@ def parse_config(self) -> dict: } # Skills + # Only the YAML frontmatter (name + description) is loaded into the + # session at startup. SKILL.md bodies load on demand when the user + # invokes the skill. Measuring the full file over-counts by ~10-20x + # and inflates skill_bloat findings. See issue #16. skills_dir = CLAUDE_DIR / "skills" if skills_dir.exists(): for sd in skills_dir.iterdir(): @@ -557,7 +587,7 @@ def parse_config(self) -> dict: if skill_md.exists(): config["skills"].append({ "name": sd.name, - "tokens": estimate_tokens_from_file(skill_md), + "tokens": _estimate_skill_frontmatter_tokens(skill_md), }) # CLAUDE.md