---
name: v0280-model-invoked-skill-bodies
description: "2026-08-21 SHIPPED — model-invoked skills (CC's Skill tool) never got their operative body; plus the probe trap where a missing hook confirm reads identically to a hook that never fired."
metadata: 
  node_type: memory
  type: project
  originSessionId: d4b9ffe5-c44f-4d39-898f-a3399266fa85
  modified: 2026-08-21T23:39:24.370Z
---

**v0.28.0 (2026-08-21), SHIPPED + FIELD-VERIFIED on HFENDULEAM.** Claude Code reaches a skill two
ways — a typed leading slash-command (UserPromptSubmit sees it) and a **description match through
its `Skill` tool** (`Skill(skill:"sptc:knock", args:…)`), which never produces a prompt. All skill
injection hung off UPS, so EVERY model-invoked `/sptc:*` call got the thin skeleton and no body —
all 12 skills, since the skeletons went thin. Fixed at PreToolUse (`skill_key_from_tool` + an
injection leg ahead of both early returns). `REQ-SKILL-TOOL-INJECTION`, KNOWN-HAZARDS §4.2.

⭐ **THE MEASUREMENT TRAP — a missing confirm is not an absence.** To ask "does PreToolUse even fire
on the `Skill` tool?" I first used a bare tag whose confirm rides PreToolUse. Nothing appeared on the
Skill call; the confirm landed on the NEXT Bash call. That reads exactly like "the hook never fired"
and is indistinguishable from "it fired before my tag reached the transcript" — the instrument's
input (the transcript) lags the event I was timing. The discriminating probe removed transcript
timing from the path entirely: **spool an inbound message** (`printf … | spt send <self>` → QUEUED),
then make the Skill call. It was delivered ON that call, under a `PreToolUse:Skill` header. Rule:
when probing whether a hook fires, drive it with state that already exists (spooled mail), never
with state the same turn is still writing.

Two more facts worth keeping:
- `tool_input.skill` carries the **plugin-qualified** name (`sptc:<id>`) plus free-prose `args`.
  A BARE name is refused on purpose — a user's own project skill named `send`/`version` must never
  pull an sptc body.
- CC dedupes the SKELETON within a session ("already loaded above; instructions unchanged") but the
  hook still fires — so an injected body arrives on every invocation regardless.

Deferred, deliberately: the 12 skeletons still say "look out for the UserPromptSubmit
additionalContext". Editing them is a cplugs skeleton release; rides the next structural plugin
change. Their one-call fallback is still correct.

See [[v0261-hints-never-called]] — same family: a declared feature that delivered nothing while
reading as shipped, found while diagnosing that one.
