[BUG] Skill frontmatter model: override applied on user-typed invocation but silently ignored when invoked via the Skill tool (registers in both cases)

Status Fixed / completed
Reported on v2.1.153
Maintainer reply None cached
Activity 1 comment · opened Jul 21, 2026 · closed Jul 21, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The SKILL.md frontmatter model: override is path-dependent: it works when the user types /skill-name, but is silently not applied when Claude invokes the same skill via the Skill tool mid-turn — even though the harness demonstrably registers the override on both paths (the Skill tool result and a command_permissions attachment both carry the resolved model ID).

This narrows #45191 (closed as not planned, reported as "model: has no effect at runtime"): the field is parsed and resolved correctly everywhere, and applied on the user-typed path — only the mid-turn application is missing. Since model-triggered invocation is the default path for model-invocable skills, cost-control uses of model: (route mechanics to a cheaper model, cap an expensive session) silently fail most of the time.

Reproduced identically on 2.1.153 (CLI, Windows 11) and 2.1.215 (desktop app).

What Should Happen?

Per the frontmatter reference: "Model to use when this skill is active. The override applies for the rest of the current turn" — regardless of whether the skill was invoked by the user or by Claude via the Skill tool.

Actual:

  • User-typed /skill-name: override applied for the rest of the turn, reverts on next prompt ✅ (matches docs)
  • Claude invokes via Skill tool: override registered (toolUseResult.model, command_permissions.model) but sampling continues on the session model ❌

Error Messages/Logs

# Both paths in one session (2.1.215, desktop app) — transcript excerpt, annotated.
# Ground truth = message.model on assistant entries in the session .jsonl.

L25 assistant model=claude-fable-5   <-- Claude invokes Skill(which-model); skill content loads
L29 assistant model=claude-fable-5       (all post-load sampling stays on session model)
L31 assistant model=claude-fable-5
L34 assistant model=claude-fable-5
L37 assistant model=claude-fable-5       "override did NOT take effect"
L41 user                             <-- user types /which-model
L46 assistant model=claude-opus-4-8      (override applied immediately)
L48 assistant model=claude-opus-4-8
L49 assistant model=claude-opus-4-8      "override TOOK EFFECT this time"
L53 user                             <-- next user prompt
L54 assistant model=claude-fable-5       (session model resumes, as documented)

# The override REGISTERS even on the failing path (2.1.153) — raw transcript lines:
"toolUseResult":{"success":true,"commandName":"land-branch","model":"claude-opus-4-8"}
"attachment":{"type":"command_permissions","allowedTools":[],"model":"claude-opus-4-8"}
# ...yet every subsequent assistant entry in the same turn keeps the session model:
L184 assistant model=claude-fable-5  (03:32:41 — after the skill + override registered at 03:32:32)
L185 assistant model=claude-fable-5
L186 assistant model=claude-fable-5
L188 assistant model=claude-fable-5
L189 assistant model=claude-fable-5

Steps to Reproduce

  1. Create a probe skill at ~/.claude/skills/which-model/SKILL.md (no context: fork):

``yaml
---
name: which-model
description: Diagnostic probe — report which model is actually sampling right now.
model: opus
---
``

  1. Set the session to a non-Opus model (tested with claude-fable-5).
  2. Ask Claude in prose to invoke the skill → it fires Skill(which-model) mid-turn.
  3. In a later turn, type /which-model yourself.
  4. Read ground truth from message.model on assistant entries in the session transcript (~/.claude/projects/<proj>/<session>.jsonl). This field comes from the API response, and it correctly flips per-message in other scenarios (see the fork control below), so it is trustworthy.

Result: step 3 keeps sampling on the session model (override ignored); step 4 switches to claude-opus-4-8 for the rest of the turn (override honored).

Claude Model

Other

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.153 (Claude Code) — reproduced identically on 2.1.215 (desktop app)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

VS Code integrated terminal

Additional Information

Control — context: fork + model: works on both paths: a skill with context: fork + model: haiku, invoked via the Skill tool from an Opus session, produced a subagent transcript whose assistant entries are all claude-haiku-4-5-20251001 (5/5). So frontmatter parsing, model resolution, and per-message message.model accuracy are all fine — the gap is specifically the inline mid-turn application of an already-registered override.

Adjacent observation: the system prompt's "You are powered by the model named …" line is frozen at session start — after /model switches or a successful skill override it keeps claiming the original model (observed claiming Fable while Opus served, and vice versa). Harmless for sampling, but it makes model self-report useless for verifying overrides; transcript message.model is the only reliable signal. Happy to split this into its own issue if preferred.

Session models involved: claude-fable-5[1m], claude-opus-4-8[1m].

Related: #45191 (closed/locked; this narrows it), #17283 (Skill tool + context: fork — works on the versions above). Supersedes #79654 (my earlier filing that bypassed the issue form).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗