[BUG] Skill frontmatter model/effort fields are parsed but not applied to API routing
Summary
The documented skill frontmatter fields model and effort (https://code.claude.com/docs/en/skills, frontmatter reference: "Model to use when this skill is active. The override applies for the rest of the current turn...") are partially applied on v2.1.221: the harness parses them and injects the target identity, but every API call in the turn is still served by the session model. The context: fork + agent path works correctly on the same build, which confirms the skill file itself is well-formed.
Environment
- Claude Code version: 2.1.221
- OS: Ubuntu 24.04 (Hetzner CX33)
- Skill scope: user (
~/.claude/skills/recallsymlink to a local repo checkout; no plugins installed,/reload-pluginsreports 0) - No
availableModelsallowlist insettings.json,settings.local.json, or managed settings - Session model: Fable 5 (
claude --model fable)
Skill frontmatter (minimal relevant excerpt)
---
name: recall
description: ...
model: claude-sonnet-5
effort: high
---
Steps to reproduce
- Add root-level
model: claude-sonnet-5andeffort: highto a user-scope skill's frontmatter. - Start a fresh session with a different model:
claude --model fable. - Invoke the skill, via slash command and/or the Skill tool (both were tested).
- Inspect the session transcript:
jq -r 'select(.type=="assistant") | .message.model' ~/.claude/projects/<slug>/<session>.jsonl | uniq -c
Expected behavior
Per the documentation, assistant entries for the remainder of the turn should be served by claude-sonnet-5, with the session model resuming on the next prompt.
Actual behavior
Evidence of partial application — the field is read, but routing never switches:
- The Skill tool result itself displays the target:
Successfully loaded skill · claude-sonnet-5. - The system prompt of the active turn declares "powered by claude-sonnet-5".
- Yet 713/713 assistant entries in the session transcript at time of filing (including 2 slash-command invocations and 1 Skill-tool invocation of the skill, plus a controlled 30-entry post-invocation window) carry
"model":"claude-fable-5". Zeroclaude-sonnet-5entries.
So the model self-identifies as Sonnet (identity injected from the frontmatter) while every response is actually served by the session model — arguably worse than ignoring the field, since the harness reports a switch that never happened.
Control test: fork path works on the same build
Changing the same skill to mechanism-B routing works on both invocation paths:
model: claude-sonnet-5
effort: high
context: fork
agent: sonnet-worker # ~/.claude/agents/sonnet-worker.md: model: claude-sonnet-5, effort: high
background: false
Result: the skill executes in a subagent whose transcript (subagents/agent-*.jsonl) shows 20/20 and 18/18 assistant entries on claude-sonnet-5, each carrying "effort":"high", while the parent session stays on claude-fable-5. (Incidentally, issue #17283 — fork/agent ignored when invoked via the Skill tool — does not reproduce on 2.1.221.)
This isolates the bug to the direct model/effort override path: same skill, same agent availability, same session — only the routing mechanism differs.
Impact
Per-skill model/effort calibration (e.g. pinning a retrieval skill to claude-sonnet-5 at high while the session runs Fable 5) silently does nothing, while the UI and system prompt claim it succeeded. Users relying on the documented behavior get miscalibrated (and potentially more expensive) execution without any signal.
Workaround
Use context: fork + a dedicated agent pinning the exact model ID and effort (works, at the cost of losing conversation history inside the skill).