Repository navigation
[Bug]: Mid-sentence Claude skill chips split the original request during native expansion #13256
Description
Activity
Triage
Confirmed. A mid-sentence Claude skill chip is sent as two SDK text blocks, and the original sentence does not survive Claude Code's expansion of the second block. This is on
v0.0.42and on currentmain(f5ef0ddb9). The latest nightly,v0.0.43-nightly.20260923.2150, is that commit, so updating does not change this.ClaudeSkillDispatch.tson that commit is blob2dc106e853828e3972ae859ed7cd6ac6cf204cb5.you will $implement fixesplans to the blocks in the report: leading textyou will(the space before the chip is trimmed), then/implement fixes.$implement fixesstays a single command block.before $implementplans tobeforeplus/implement.please $implement the fixes, then a following paragraph, puts that paragraph on the command (/implement the fixes\n\nThen report the result).The split is
planClaudeSkillDispatch.buildUserMessageEffectpushes the leading text first and the/nameblock last. The test "moves a mid-prompt mention into a trailing slash command" locks that pre-expansion shape in (ClaudeSkillDispatch.test.ts). A literalyou will /implement fixesis not a chip, so the planner leaves it as one block, which matches the report's plain-mode result (sentence kept, skill not expanded).The file header says text on either side stays in order (
ClaudeSkillDispatch.ts). That describes the SDK blocks only. The suffix is appended to the slash command, and Claude Code expands a skill from the last text block when it starts with/. This environment has noclaudebinary, so the native reorder (command metadata, then the orphaned prefix, then the skill body) is the capture in the report. Nothing in-tree asserts that post-expansion message.Two notes:
- The command block is pushed at lines 1671–1672, after image blocks, outside the cited 1600–1620 range. Images sit between the prefix and
/implement .... The minimal repro needs no attachments. - Text after the chip showing up as
<command-args>andARGUMENTSis the planner:commandTextis/${name}plus the remainder, newlines included. That is one expansion.
The same split happens with no user prefix when effort is Ultrathink.
buildPromptTextrunsapplyClaudePromptEffortPrefixbefore dispatch.$implement fixesbecomesUltrathink:\n$implement fixes, which plans to leadingUltrathink:and command/implement fixes. The slash-command exemption only skips text that already starts with/. A$chip does not qualify. Chip-first avoids a user-authored prefix, and still splits while Ultrathink is selected.you will $implement fixeswith Ultrathink plans to leadingUltrathink:\nyou will.No open issue describes this reorder. #9128 (merged, fixes #7671) introduced the split so a chip anywhere would expand. anthropics/claude-code#87113 is the one-skill-per-message limit.
The workaround holds at a normal effort level: put the chip first, then write the instruction. With Ultrathink, that still produces a leading
Ultrathink:block.Extra newlines in the prefix block cannot repair the sentence. Expansion requires the last text block to start with
/. The intact request has to live in the earlier block (chip rewritten in place), with the message still ending in the/nameblock so the skill loads. Today that earlier block is only the trimmed prefix, with the skill token removed.- The command block is pushed at lines 1671–1672, after image blocks, outside the cited 1600–1620 range. Images sit between the prefix and
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.acceptedfeature request acceptedfeature request acceptedvia-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 23, 2026 A related case that #13299 won't cover: the
$namein my prompt was never meant as a skill.I use
$namein prompts to refer to a project (a convention across my sessions:$vylety= the project in~/Projects/vylety). Several of my projects have a skill with the same name, so the composer turns$vyletyinto a chip on the space andClaudeSkillDispatchsends it as/vylety. The promptprocess specification in $vyletyreached Claude Code as the textprocess specification infollowed by the expanded skill body with no arguments. The model read that as a cut-off message and ran the wrong skill.With #12098 this now applies to
€ £ ¥ ₹ …too, and there is no setting for it:showSkillsInSlashMenuonly affects the/menu, and the only other lever isskillOverrides: "off", which disables the skill in Claude Code itself.Would you consider one of these:
- A setting to turn off
$/currency skill mentions (the/menu still works for invoking skills), or - Dispatching only chips that were picked from the
$menu, and leaving typed$wordas plain text even when it matches a skill name.
Right now the workaround is to write
$vylety,or`$vylety`so the token pattern doesn't match.- A setting to turn off
Before submitting
Area
apps/server — Claude provider skill dispatch (reported from desktop).
Summary
A skill chip used as a verb in the middle of a sentence changes the structure of the request sent to Claude. For example,
you will $implement fixesbecomes command metadata for/implement fixes, followed by the orphaned textyou will, followed by the expanded skill. The original sentence is absent from the model request.This is an integration problem between T3's mid-prompt dispatch and Claude Code's native expansion. The rearrangement happens before inference; the reproduction below captures the actual HTTP request with a local fake API, without asking a model to reconstruct its input.
Steps to reproduce
Install a minimal project skill at
.claude/skills/implement/SKILL.md:In a Claude thread, type
you will, insert theimplementskill chip, then typefixes.Send and inspect the provider transcript or outgoing Messages request.
Compare with a message starting with the chip:
$implement fixes.Expected behavior
Preserve the complete user request, including the relationship between the prefix, the skill mention, and the suffix, while invoking the selected skill. A chip displayed inside a sentence should not silently turn that sentence into a separate command plus an incomplete fragment.
Actual behavior
T3 emits these SDK content blocks:
[ {"type":"text","text":"you will"}, {"type":"text","text":"/implement fixes"} ]Claude Code 2.1.280 expands them into these text blocks in the outgoing model request (unrelated system reminders omitted):
The prefix and skill body are separate text blocks, not a literal
you willBase directorystring. Flattening them without separators gives that appearance, but the confirmed defect is the loss of the original sentence and movement of the invocation ahead of its prefix.With a longer message, every paragraph after the chip becomes command arguments. Its appearance in both
<command-args>and the skill'sARGUMENTSis native expansion behavior, not evidence of two executions.Diagnosis and controls
The dispatch was introduced in #9128. At the investigated revision:
planClaudeSkillDispatchseparates the prefix from the last selected skill and removes its trailing whitespace.buildUserMessageEffectputs that prefix in an earlier text block so Claude expands the final/nameblock.you will /implement fixes$implement fixesThe smallest trigger is a prefix plus the invocation (
before $implement); trailing arguments, multiple paragraphs, attachments, plugins, and conversation history are unnecessary.This belongs first in T3 because T3 deliberately converts an inline mention into an unconditional slash invocation. Claude's native multi-block expansion explains the resulting order. Plain mid-line CLI input is not equivalent and does not auto-expand. I have not established that Claude violates a documented multi-block ordering guarantee.
Impact
Minor bug or occasional failure. The original reported turn was understood, with no observed work loss. A prefix containing a qualification or negation could become ambiguous after this transformation; no model misinterpretation or unintended execution was demonstrated by this fixture.
Version or commit
f5ef0ddb90a8c36584e181b1913e7b8a5df30ffc.mainwhen checked:2dc106e853828e3972ae859ed7cd6ac6cf204cb5.2.1.280, also recorded in the original transcript.27.0.0; Node24.21.0.0.0.43-nightly.20260923.2135. The exact desktop build at the time of the original turn was not independently established.Verification limits
The eight existing
ClaudeSkillDispatchtests pass; they assert the pre-expansion split. The native CLI fixture captures the downstream behavior they do not cover. The existing adapter test could not load in this checkout (SchemaTransformation.transformEffect is not a function; installed Effect beta.103 versus declared rc.115), so I do not claim that suite passed. No browser or desktop automation was performed for this investigation.Workaround
Put the skill at the start of the message, then write a complete instruction after it, instead of using the chip as a word inside a sentence:
Executable reproduction
The script below uses a temporary project/config, disables tools and hooks, captures a loopback Messages request, and replies locally. It does not need provider credentials or call a real model. All fixtures and the local server are removed when the process exits.
Save it as
repro.pyand run:From a T3 checkout,
--mode t3 --repo "$PWD"additionally imports and runs the actual planner (Node 24 required). Default mode supplies the same two-block input directly to Claude Code.Python fixture (standard library only)