server-desktop-01/docs/cc-tool-audit.md

269 lines
13 KiB
Markdown
Raw Normal View History

# Claude Code tool & context audit
**Date:** 2026-06-30
**Question:** Are too many built-in CC tools loaded into context, slowing sessions and
hurting focus? Which can be excluded globally vs. situationally?
**Method:** Mechanically parsed every tool-call (`tool_use`) across **224 main
sessions + 1,946 subagent transcripts = 2,170 transcripts** in `~/.claude/projects/`,
cross-referenced against the full tools reference
(https://code.claude.com/docs/en/tools-reference.md), and measured the actual
bytes loaded into one representative session's startup context.
---
## TL;DR — the hypothesis is mostly outdated
Excluding **built-in tools** used to meaningfully shrink the prompt. It no longer
does, because Claude Code now **defers** rarely-used tool schemas (tool search):
they load on demand via `ToolSearch`, not at startup.
- Total *static* startup context in a typical session here ≈ **6,300 tokens**.
- Built-in tool schemas are a small slice of that; **MCP cost = 0** (no MCP servers
configured). **Skills (50) ≈ 3,700 tok** are the single largest contributor.
- Of **24 built-in tools never used in 2,170 transcripts**, only **one** (`Workflow`)
is both always-loaded *and* unused. The other 23 are already deferred, platform-
impossible, or trivially small.
So: a confident global *tool* exclusion list exists, but its payoff is small. The
real levers for "feels slow / unfocused" are elsewhere (see §5).
> ⚠️ Honest caveat: trimming context lowers prefill latency and cost and may sharpen
> focus, but it does **not** change Opus's per-token generation speed. Opus is simply
> slower to generate than Sonnet/Haiku; no amount of tool pruning fixes that.
---
## 1. What tools actually get used (2,170 transcripts)
| Tool | Calls | Transcripts using it |
|---|---:|---:|
| Bash | 9,168 | 1,248 |
| Read | 5,955 | 1,185 |
| Edit | 1,983 | 501 |
| Agent | 1,948 | 541 |
| Write | 981 | 382 |
| WebFetch | 288 | 72 |
| ToolSearch | 169 | 139 |
| WebSearch | 124 | 36 |
| AskUserQuestion | 108 | 70 |
| Skill | 104 | 84 |
| TaskUpdate / TaskCreate | 54 / 31 | 10 / 7 |
| SendMessage | 40 | 27 |
| Monitor | 21 | 12 |
| TaskStop / ScheduleWakeup | 8 / 4 | 5 / 3 |
| RemoteTrigger / ReportFindings | 1 / 1 | 1 / 1 |
**Never used anywhere (0 calls):** `Artifact`, `CronCreate/Delete/List`,
`EnterPlanMode/ExitPlanMode`, `EnterWorktree/ExitWorktree`, `Glob`, `Grep`,
`ListMcpResourcesTool`, `LSP`, `NotebookEdit`, `PowerShell`, `PushNotification`,
`ReadMcpResourceTool`, `SendUserFile`, `ShareOnboardingGuide`,
`TaskGet/TaskList/TaskOutput`, `TodoWrite`, `WaitForMcpServers`, `Workflow`.
> Note `Grep`/`Glob` show **0** — search runs through `Bash` (ripgrep/find directly,
> reinforced by the RTK proxy). They are still worth keeping (see §4).
---
## 2. The decisive cross-reference: never-used ∩ actually-loaded
The "never used" list conflates three very different things. Only the first bucket
gives back real context when excluded.
| Bucket | Tools | Exclusion payoff |
|---|---|---|
| **Always-loaded & unused** | `Workflow` | **Real** — large schema |
| **Already deferred** (loaded on demand, not at startup) | `CronCreate/Delete/List`, `EnterPlanMode/ExitPlanMode`, `EnterWorktree/ExitWorktree`, `LSP`, `NotebookEdit`, `PushNotification`, `TaskGet/List/Output` | **~0** — not in the prompt anyway |
| **Platform/plan-impossible or not loaded here** | `PowerShell` (Linux), `Artifact`/`ShareOnboardingGuide` (need Team/Ent.), `SendUserFile`/`PushNotification` (need Remote Control), `TodoWrite` (disabled by default), `WaitForMcpServers` (only when tool-search is OFF), `Glob`/`Grep`, MCP-resource tools | **Cosmetic** — either absent or trivially small |
**This is why manual exclusion mattered more in the past:** tool-search deferral now
does the trimming automatically.
---
## 3. Where the startup context actually goes (~6,300 tok measured)
| Contributor | Est. tokens | # items |
|---|---:|---:|
| **Skills** (name + description) | ~3,700 | 50 |
| **Subagent types** | ~917 | 18 |
| CLAUDE.md (project) | ~842 | 1 |
| MEMORY.md auto-index | ~427 | 1 |
| RTK.md | ~241 | 1 |
| CLAUDE.md (global) | ~148 | 1 |
| **MCP tool schemas** | **0** | 0 servers |
| **Total** | **~6,300** | |
Skills + agents = ~74% of static context. Heaviest single items: the built-in
`claude-api` skill (~269 tok), personal `the-humanizer` (~239), `impeccable` (~226),
`memsearch:memory-recall` (~185).
---
## 4. Recommendation A — global tool exclusions (small but safe)
Add to `~/.claude/settings.json` `permissions.deny`. The mechanism: a denied tool is
removed from the advertised set (its schema isn't loaded), per the tools reference
("to disable a tool entirely, add its name to the deny array"). Verify by checking the
session token count before/after if you want proof.
```jsonc
{
"permissions": {
"deny": [
"Workflow", // 0/2170 uses; largest always-loaded built-in schema.
// KEEP it ONLY if you use /workflows or "ultracode".
"PowerShell", // Linux host — never available. Cosmetic.
"Artifact", // needs Team/Enterprise plan. Cosmetic.
"ShareOnboardingGuide" // paid team feature. Cosmetic.
]
}
}
```
- **`Workflow` is the only entry with real payoff.** It is the multi-agent
orchestration tool (the big JS-script one) — *not* `Agent`. You use `Agent`
constantly (1,948 calls) and `Workflow` never. Keep `Agent`; drop `Workflow`
unless/until you adopt `/workflows`.
- **Do NOT deny** `Glob`/`Grep` (tiny; the sanctioned search path — denying pushes
everything to `Bash grep`/`find`, which can add permission prompts), anything
**deferred** (no savings), or plan-mode/worktree/Task tools (deferred *and*
UI-driven — denying `ExitPlanMode` would break Shift-Tab plan mode).
Net context saved: roughly the `Workflow` schema. Honest, but modest.
---
## 5. Recommendation B — the real lever: situational plugins/skills
Skills/agents (~4,600 tok) dwarf the built-in-tool headroom, and they load *every*
session regardless of relevance. These are controlled per-machine via
`enabledPlugins` in `~/.claude/settings.json` (not easily per-project), so the
practical move is toggling plugins by what you're doing.
Currently enabled: `superpowers` (already disabled ✓), `ruby-lsp`, `perspectives`,
`git-context`, `invoice-ninja`, `memory`, `memsearch`, `doc-hygiene`, `codex`.
| Plugin / skill group | Relevant to | Drop when not… |
|---|---|---|
| `codex:*` (5 skills + agent) | coding handoffs to Codex | …coding |
| `ruby-lsp` | Ruby projects | …in a Ruby repo |
| `perspectives:*` (9 agents) | design/plan critique | …planning/designing |
| `invoice-ninja` | freelance billing | …doing client billing |
| `deep-research`, web tools | research | …researching |
| personal writing/UI: `the-humanizer`, `impeccable`, `edit-article`, `prototype` | content/frontend | …writing or building UI |
| `doc-hygiene:*` | docs repos | …maintaining docs |
| `memory:*` + `memsearch:*` | cross-session recall | rarely — but see below |
**Suggested situational profiles** (toggle `enabledPlugins`):
- **Coding repo:** keep `codex`, `ruby-lsp` (if Ruby), `git-context`. Drop
`invoice-ninja`, writing/UI skills, `deep-research`, `perspectives`.
- **Freelance / PM / client:** keep `invoice-ninja`, `git-context`. Drop `codex`,
`ruby-lsp`, `perspectives`, coding skills (`tdd`/`diagnose`/`run`/`verify`).
- **Research / writing:** keep `deep-research`, writing skills. Drop `codex`,
`ruby-lsp`, `invoice-ninja`.
- **Infra / sysadmin (this repo):** Bash/Read/Edit-heavy. Drop writing/UI,
`invoice-ninja`, `codex`, coding frameworks; keep `doc-hygiene`, `git-context`.
> Reality check: even the full ~6.3k static context is small next to a working
> session that fills 50k150k+ tokens. Trimming skills is good hygiene and helps
> focus, but it is not where most of a slow session's time goes.
---
## 6. What is probably actually slowing sessions
Ranked by likely impact, from the data:
1. **Opus generation speed.** Inherent; switch to Sonnet/Haiku for mechanical work,
or use `/fast` (faster Opus output) for interactive coding.
2. **Heavy `Agent` delegation.** 67% of sessions spawn subagents; 541 transcripts
used `Agent`. Each subagent is a fresh context with its own prefill + sequential
wall-clock wait. Great for big fan-out; pure latency for small lookups you could
do inline.
3. **Per-prompt / per-tool hooks.** The `memory` plugin registers all 5 hook types,
incl. **PostToolUse (runs after every tool call)** and SessionStart/UserPromptSubmit
(the large memory dump at session start). `memsearch` adds more. This is a
per-operation tax that tool-count pruning won't touch — worth timing/disabling to
test. **Most promising thing to measure next.**
4. **Long sessions** → context summarization passes. Use `/clear` between unrelated
tasks rather than carrying one giant session.
5. Static tool/skill context (this audit) — real but the **smallest** of these.
---
## Appendix — four representative sessions (loaded vs. used)
1. **Coding** (`llf-schema/19320d96`) — implement an OpenSpec change. 191 calls, only
**Edit/Read/Write/Bash**. No web, no `Agent`, no skills. Pure local code loop.
2. **Infra, wide** (`proxmox-hermes-01/c24faa0a`) — plan a Proxmox agent setup.
Used nearly the whole belt: Bash/Agent/WebFetch/WebSearch/Read/Edit/Write/
AskUserQuestion/Skill(`git-context:repo-init`). The legit case for keeping web
tools loaded.
3. **Infra, ops** (`systems-admin/ffed234f`) — "how did the backup do this week?"
50 calls, only **Bash/Read/Edit/Write**. Textbook shell-only profile.
4. **PM / orchestration** (`ovh-prod-01/991913d9`) — decide next server-consolidation
phase. Lead agent used only **Agent + AskUserQuestion**; 360+ tool calls happened
in 40 subagents. Never touched a file or shell itself.
Three of four would have been unaffected by stripping every web tool, every skill,
and (for #1/#3) all delegation tooling.
---
## Addendum (2026-06-30) — follow-up: skills, hooks, and orchestration
### Skill loading — there IS a native lever (no "dispatcher" needed)
Per the docs: a skill's **body** loads only on invocation ("costs almost nothing
until you need it"); only **name + description** load at startup, and that listing is
**budget-capped** (`skillListingBudgetFraction`, ~1% of context; `skillListingMaxDescChars`
default 1,536/skill). There is **no** skill-level `ToolSearch` equivalent.
To reduce always-loaded skill weight, use **`skillOverrides`** in settings rather than
a community dispatcher skill:
- `"name-only"` — keep the skill available but drop its description from context.
- `"off"` — hide it entirely.
Plus `enabledPlugins`, `disableBundledSkills: true`. The dispatcher/index pattern is
**not** officially recommended — the budget + `skillOverrides` already do it.
### Hooks are synchronous but cheap (measured on this machine)
Hooks block the turn until exit (up to `timeout`); `"async": true` makes one
fire-and-forget. The `memory` plugin's hooks are synchronous but fast:
`post_tool_use_write` ~38 ms (Write|Edit only, early-exits off-vault),
`session_context` ~33 ms/prompt (injects ~0 tokens normally),
`rtk hook` ~24 ms/Bash. Negligible vs. multi-second model turns. **Not a latency
source.** The big memory dump is SessionStart-only (once).
### Orchestration audit — subagent model mismatch (1,962 transcripts)
Model is recorded per subagent transcript. Distribution: **Sonnet 36.7% · Haiku 36.5%
· Opus 26.6%**. By work profile:
| Subagent model | n | % that did only file-edit/shell work |
|---|---:|---:|
| Opus | 521 | 53% (+38% spawned their own subagents) |
| Sonnet | 720 | 64% |
| Haiku | 716 | 83% (well-matched) |
**Finding:** 27% of subagents run on **Opus**, most doing mechanical edits. Root cause:
when an `Agent` spawn omits `model`, the subagent **inherits the parent model (Opus)**
so the CLAUDE.md routing table is advisory and leaks.
**Subagent model resolution order** (docs): `CLAUDE_CODE_SUBAGENT_MODEL` env →
per-invocation `model` param → subagent frontmatter `model:` → inherited main model.
**Enforcement fix:** set **`CLAUDE_CODE_SUBAGENT_MODEL=haiku`** (or `sonnet`) so
subagents default cheap instead of inheriting Opus; pin per-type `model:` frontmatter
for specialized agents; escalate by explicit criteria only.
**Instruction tuning (pilot on one project first):**
1. Drop "no minimum complexity threshold" — direct-operate trivial single-file ops;
delegate only when parallelizable, large, or context-heavy. Mandatory delegation of
one-line edits is a net latency *loss*.
2. Make routing enforced, not advisory (env var above + "always pass model on spawn").
3. Relax "never read before delegating" — allow a cheap orienting read when paths/
structure are uncertain, to avoid blind-spec retry loops.