[BUG] Skill frontmatter model/effort fields are parsed but not applied to API routing

Status Open
Reported on v2.1.221
Maintainer reply None cached
Activity 0 comments · opened Aug 5, 2026

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/recall symlink to a local repo checkout; no plugins installed, /reload-plugins reports 0)
  • No availableModels allowlist in settings.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

  1. Add root-level model: claude-sonnet-5 and effort: high to a user-scope skill's frontmatter.
  2. Start a fresh session with a different model: claude --model fable.
  3. Invoke the skill, via slash command and/or the Skill tool (both were tested).
  4. 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". Zero claude-sonnet-5 entries.

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).

View original on GitHub ↗