[BUG] command.describe isHidden ignored in desktop app slash menu (SDK command list doesn't filter isHidden)

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

Summary

A plugin's command.describe hook that returns isHidden: true hides the command in the terminal typeahead, but the command still appears in the Claude desktop app (Code tab) / menu. The command list the CLI publishes to SDK hosts ignores isHidden entirely.

Version

Claude Code 2.1.286 (the CLI bundled with the Claude desktop app, macOS)

Repro

  1. A plugin ships namespaced skills (e.g. my-plugin:plan, my-plugin:tasks) that are meant to be reached only through one registered command (/my-plugin <verb>), and hides them with:

``ts
on('command.describe', async ($, e, next) => {
const result = await next(e)
if (e.command.startsWith('my-plugin:')) return { ...result, isHidden: true }
return result
})
``

  1. In a terminal claude session, type /my-plugin — the my-plugin:* skills are hidden. ✅
  2. In a desktop Code tab session with the same plugin enabled, type /my-plugin — all the my-plugin:* skills are listed. ❌

Cause (from reading the 2.1.286 bundle)

The terminal typeahead skips hidden commands (if (r.isHidden) continue), after the command.describe hook has been applied.

The SDK-facing list (used for the initialize response's commands, commands_changed, and the init message's slash_commands) filters only on userInvocable and the "off" state:

function vAr(e){ return e.userInvocable !== false && !Tx(e) }
function Vnn(e){ return Al(e,"name").filter(vAr) }
function k4(e){ return Vnn(e).map(tsn) }   // tsn -> {name, description, argumentHint, aliases, builtin}

isHidden is neither filtered on nor serialized, so the desktop host has no way to hide these commands. The same applies to any command hidden via isHidden, not only plugin skills.

Expected

Commands that command.describe (or a command's own isHidden) marks hidden should be left out of the SDK command list, or carry an isHidden flag the host can honor, matching the terminal typeahead.

Why the obvious workaround doesn't work

Setting user-invocable: false on the skills does remove them from the SDK list, but then the slash-command path refuses to run them ("This skill can only be invoked by Claude, not directly by users."), which breaks a plugin dispatching them via $.command.run. Hidden-but-runnable is exactly what isHidden documents ("a hidden command still runs when typed in full"), so the SDK list should respect it.

View original on GitHub ↗