[Desktop] Slash commands registered by mods via $.command.register show "isn't a command here" and are missing from the / menu

Status Open
Reported on v2.1.286
Maintainer reply None cached
Activity 3 comments · opened Oct 5, 2026

What happens

A mod (function-hook plugin) registers a slash command with $.command.register in its session.start hook. In the desktop app's Code tab, typing that command shows the orange warning "/kit isn't a command here." in the composer. The command still runs: the plugin's command.run hook answers, the pane opens and the text output prints. So the warning is wrong, and the command is also missing from the composer's / menu.

Steps to reproduce

  1. Install a mod that registers a command at session start, for example:

``sh
claude plugin marketplace add effectustasi/kit
claude plugin install kit@kit
`
It calls
await $.command.register({ name: 'kit', description: '…' }) in on('session.start', …), and serves it with on('command.run', { command: 'kit' }, …)`.

  1. Open a new Code session in the Claude desktop app (Windows).
  2. Type /kit and send it.

Expected: /kit is listed in the / menu and accepted without a warning, as the $.command.register docs say ("listed in the typeahead from the next keystroke on").
Actual: the composer shows "/kit isn't a command here." The command runs anyway.

What we tried

We added a markdown command commands/kit.md to the plugin so the name would be known at startup. It was listed only as /kit:kit, and it went to the model as a prompt instead of reaching the plugin's command.run { command: 'kit' } hook. So a static command can't be used to work around this.

Guess

The desktop composer seems to validate against the command list it receives when the session starts, before mods' session.start hooks finish registering. It doesn't pick up commands registered later.

Environment

  • Claude Code 2.1.286 (bundled with the desktop app); CLI 2.1.287
  • Claude desktop app, Code tab, Windows 10 Pro
  • Reproduction: https://github.com/effectustasi/kit
  • Related: #91870 (Mods)

---
_Generated by Claude Code_

View original on GitHub ↗

3 Comments

arasovic · 8 days ago

Same on macOS. I have some extra data that may help narrow it down.

Environment: Claude desktop app 2.26454.2, Claude Code 2.1.293, macOS 26.7, Code tab, "No folder" session.

Repro mod: claude plugin marketplace add arasovic/claude-code-mods, then claude plugin install show-me@claude-code-mods. It registers /show-me with $.command.register in session.start.

What I see

| | Mod command (/show-me) | File-based commands (/zoom-out skill, /ponytail:ponytail-gain) |
|---|---|---|
| / menu before the first message | missing | listed |
| Warning on the first message | shown | not shown |
| / menu after the first reply | listed | listed |

So here the composer does list the registered command after the session's first reply. The wrong warning comes from the command list it uses before the session is live.

Likely cause (inferred from the desktop app's bundled code, not confirmed)

The pre-session command list appears to come from a temporary CLI query that the desktop app starts with settings: { disableAllHooks: true }. With hooks off, no mod's session.start runs, so nothing calls $.command.register.

The CLI side is easy to check. Start claude --input-format stream-json --output-format stream-json --verbose --await-initialize and send an initialize control request:

  • with no extra settings: 153 commands
  • with --settings '{"disableAllHooks":true}': 147 commands

The six missing ones are exactly the mod-registered commands (show-me, changes, ctx, pin, unpin, timeline). File-based plugin and skill commands are in both lists.

Keeping hooks off for that temporary query makes sense. A composer-side fix may be simpler, for example: do not show the warning until the session's live command list has arrived.

petekp · 3 days ago

Same on macOS 15.7 with Claude Code 2.1.296 in the desktop app, with one difference: the refused first message does not run the command.

Mod: registers /inbox with $.command.register and serves it with on('command.run', { command: 'inbox' }, …).

What I see in a new local Code session:

| Message | Composer | Result |
|---|---|---|
| /inbox demo as the first message | "/inbox isn't a command here." | Sent to the model as a prompt. The plugin's command.run hook does not answer, and the model starts a turn. |
| /inbox demo after the first reply | /inbox listed in the / menu, no warning | Runs as a command. |

So on 2.1.296 the first message costs a model turn instead of only showing a wrong warning. The second half matches the table above: the command list is right once the session is live.

_Written by Claude_

rollingbd · 19 minutes ago

Also reproduces on macOS (Claude desktop, Code tab, bundled Claude Code 2.1.299).

  • A plugin hooks module registers commands in session.start with $.command.register({ name, description }) and answers them with on('command.run', { command }, …).
  • On the previous bundled build (2.1.295) the commands worked in the same session — typing /tsaglabar ran the hook and opened its pane.
  • After the app updated to 2.1.299 and restarted, every such command (/tsaglabar, /tasks, /tasks-pane, /fm-agents) is sent to the model as plain text; the session notes "no command with that name is available". Restarting the app again does not help.
  • The same module is clearly loaded: its AbovePrompt band renders and its buttons call the same functions the commands do (a button that opens the pane works).
  • claude plugin validate on the plugin passes and lists command.run{command=…} hooks.

So only the composer's recognition of mod-registered commands seems to be missing on Desktop.