Repository navigation
Composer offers skills and slash commands that cannot run #7671
Description
Activity
Confirming this with another concrete Claude vs Codex UX comparison.
With Codex, the composer behaves consistently:
$lists skills, and selecting one inserts the native$skill-nameinvocation./lists commands.
With Claude, the same UI becomes misleading:
$lists skills, but selecting a user-invocation-only skill inserts$skill-nameas plain text. Claude then refuses to launch it because it was not directly invoked by the user./lists both Claude skills and commands, and selecting/skill-nameworks.
Reproduced with
setup-matt-pocock-skills: selecting it from$produced$setup-matt-pocock-skills, after which Claude explicitly said the skill required direct/setup-matt-pocock-skillsinvocation.So the problem is not merely failed execution. The provider-neutral
$picker actively leads Claude users toward the invalid path while the valid path already exists under/. The behavior proposed in #7673, keeping user-invocation-only Claude skills out of$and available under/, matches the expected UX.One more UX consequence: moving Claude user-invocation-only skills from
$to/is not semantically equivalent in T3.Provider slash-command autocomplete is intentionally available only when
/opens the message. A skill mention using$, however, can be inserted naturally anywhere in the prompt, for example:ok, now $implement all the ticketsTyping
/implementin that same position does not autocomplete, and Claude slash expansion itself only works when the command opens the message. So #7673 is a useful safety fix, but it also removes inline skill composition for Claude users.The stronger provider-agnostic UX would be:
$skill-namealways means a skill invocation in T3, in any supported composer position./commandalways means a command.- T3 preserves the skill invocation as structured composer/send metadata instead of flattening it immediately to plain
$skill-nametext. - Each provider adapter translates that canonical invocation into the provider-native mechanism and validates provider limitations.
Today
ComposerSkillNode.getTextContent()flattens the semantic skill node to$${skillName}before provider dispatch. That works natively for Codex but leaves Claude with plain prose rather than an explicit user invocation. Exposing Claude’s internal/skillconvention directly in the shared UI leaks provider semantics and gives different composition capabilities depending on the selected provider.Note
🤖 Claude Opus 5 writing on behalf of Rodrigo
#7673 now covers all three items in this issue, and @marcob896's follow-up changed the design for the better — thank you for it.
On inline composition. You were right that gating skills behind a message-start
/would remove it, and the branch did exactly that for a while. It no longer does.$nameis the default insertion from either menu, sook, now $implement all the ticketsis what picking a skill mid-message gives you./nameis reserved for the one case a mention provably cannot reach: a user-invocation-only skill picked at the start of a message.I did not want to assert the
$half from reading code, so I measured it against the Agent SDK. A model-invocable skill named mid-line runs via theSkilltool 2/2. Adisable-model-invocationskill named mid-message still runs roughly one time in three, because the tool accepts it by name even though it is hidden from the published command list — which is why those skills are the only ones routed to/, and only where/expands.That falls short of the canonical-invocation model you described.
ComposerSkillNode.getTextContent()still flattens to$${skillName}before dispatch, and per-provider translation of a structured invocation is a larger change than this fix. I would rather that be its own issue than get folded in here; it is the right direction and it is not blocked by anything this PR does.On the three items above:
disable-model-invocationis parsed during discovery and surfaces asuserInvocationOnly. Those skills are out of$.skillOverridesis read from the merged settings chain — user, project, local, then managed policy, which wins. Skills set to"off"are out of both menus;"user-invocable-only"is treated the same as the frontmatter flag.- Provider slash commands are only offered when the trigger opens the message. Built-ins like
/modeland/planare applied locally on selection, so they stay available anywhere.
Two further mismatches with the CLI turned up while verifying, both fixed on the branch: skills are identified by their directory, not frontmatter
name(a skill inprobe-alias/declaringname: probe-alias-frontmatteris published asprobe-alias, and onlyskillOverrides["probe-alias"]disables it), and name collisions resolve user over project, which is the opposite of what the code did and of what its own comment claimed. Both were measured againstclaude2.1.250 rather than inferred.Note
🤖 Claude Opus 5 writing on behalf of Rodrigo
@marcob896 — correcting myself on one thing: I said your canonical-invocation model "deserves its own issue". Wrong venue.
CONTRIBUTING.mdreserves issues for bug reports and sends proposals to Ideas discussions. That is where it belongs, and it is your proposal to file, not mine to relocate.I should also be straighter about what my fix does to the case you raised, because it is not neutral. Past the start of a message the
/menu now narrows to skills a$mention can actually reach, which excludesdisable-model-invocationones. So those skills are unreachable from the picker mid-message — previously they were offered and inserted$name, which I measured starting them about one time in three. I traded a flaky path for no path.I think that is the right trade for this issue specifically, since the reported harm was an agent silently running a different skill and cutting a release. A menu that does nothing is safer than a menu that does something else. But it is a capability reduction, it is exactly the inline-composition loss you flagged, and your model is the thing that would give it back properly rather than by picking a better sigil.
Related implementation: #8336 preserves selected
$skilltokens as structured invocation metadata and dispatches them explicitly across Codex, Claude Code, Cursor, Grok, and OpenCode. It addresses the flattened/inert skill path and user-invocation-only behavior described here. This issue should stay open because #8336 does not cover every separate slash-command picker and expansion case.
The composer pickers offer entries that are impossible to invoke, and picking one fails silently.
1.
$offers skills the agent cannot seeA skill whose frontmatter sets
disable-model-invocation: trueis deliberately hidden from the agent's skill tool, butdiscoverClaudeSkillsonly readsnameanddescription, so the$picker lists it anyway.$inserts the name into the prompt as plain text — a hint, not an invocation — so the agent still chooses. Unable to see the skill you named, it picks the closest one it can see.Concretely: I picked
$re-release-versionfrom the$menu. The agent could not see it, ranrelease-versioninstead, and cut a whole new release before I noticed.2.
$offers skills the user switched offskillOverridesinsettings.jsonis not read, and discovery hardcodesenabled: true. Every skill set to"off"still shows up.3.
/offers commands where they will not expandThe
/menu opens on any line starting with/, but the CLI only expands a slash command that opens the whole message. Chosen later in a message, the command reaches the agent as literal text.Verified against the Agent SDK with tools disabled, so the reply could only come from expansion:
/mycommandhello\n/mycommandplease run /mycommandI don't recognize /mycommand ... if it were a valid registered command, it would normally be expanded before I see it.Expected
A picker should only offer entries that will actually run. User-invocation-only skills belong in
/, which does start them; disabled skills belong in neither; and provider commands should only be offered where the CLI will expand them.Environment
macOS, Claude provider,
claudeCLI 2.1.237.