Slash menu: the top-5 most-used hoist never fires in the desktop app (frecency bucket returns empty)
Summary
The slash-command menu already implements a "top 5 most-used commands first" hoist when the filter is empty (i.e. you just typed /). In the Claude desktop app on Windows that bucket comes back empty, so the menu opens straight into built-ins followed by a long alphabetical run. All three preconditions the hoist checks are satisfied in my session, which is why I think this is a bug rather than a missing feature.
With a plugin installed that contributes 73 menu entries, this is the difference between a usable menu and one where every invocation requires typing a namespaced prefix.
Environment
- Claude desktop app 1.24012.9 (x64, MSIX), Windows 11 Home 26200
- Bundled engine:
%APPDATA%\Claude\claude-code\2.1.219\claude.exe(ProductVersion 2.1.219.0) - Plugin:
agent-orchestra@agent-orchestrav3.4.9 (18 commands + 55 skills = 73 namespaced entries)
The ordering the engine implements
In the empty-filter branch of the menu builder (symbol names below are from the minified bundle, so they will differ between builds):
if (r === "") {
let d = t.filter(T => !T.isHidden && !Hz(T)),
p = [],
f = d.filter(T => T.type === "prompt")
.map(T => ({cmd: T, score: lNt(T.name)}))
.filter(T => T.score > 0)
.sort((T, C) => C.score - T.score);
for (let T of f.slice(0, 5)) p.push(T.cmd);
let m = new Set(p.map(T => Dli(T)));
d.forEach(T => {
if (m.has(Dli(T))) return; // hoisted entries are removed from later buckets
...
});
return [...p, ...g, ...y, ..._, ...E, ...A].map(...)
}
So the intended order is: top-5 by frecency, then built-ins, then ~/.claude/commands, then <project>/.claude/commands, then policy commands, then everything else alphabetically.
The scorer is a standard frecency function - lifetime usageCount decayed with a 7-day half-life, floored at 0.1:
function lNt(e) {
let r = bt().skillUsage?.[e];
if (!r) return 0;
let n = (Date.now() - r.lastUsedAt) / 86400000,
o = Math.pow(0.5, n / 7);
return r.usageCount * Math.max(o, 0.1);
}
The three preconditions are all satisfied
type === "prompt"- the plugin command/skill loader returns{type: "prompt", name: e, ..., isHidden: !G}, so plugin-contributed commands qualify.- Key match - the loader builds the name as `
${plugin.name}:${basename(file).replace(/\.md$/,"")}, e.g.agent-orchestra:experience. The counter is incremented with that same value (if (f.type === "prompt" && f.userInvocable !== false) GJr(f.name)), andlNtreadsskillUsage[name]`. Write key and read key are the same by construction. score > 0- computed live from my~/.claude.json(last written the same day):
| name | usageCount | days since use | score |
| --- | --- | --- | --- |
| agent-orchestra:experience | 275 | 0.0 | 273.96 |
| agent-orchestra:design | 250 | 0.0 | 249.86 |
| agent-orchestra:plan | 227 | 3.2 | 164.70 |
| agent-orchestra:orchestrate | 190 | 2.8 | 144.48 |
| agent-orchestra:review-github | 135 | 0.1 | 133.84 |
Next highest is 14.94, so the top 5 is unambiguous and stable.
Observed behavior
Opening the menu with / and no filter, the first entries are built-ins (schedule, model, workflows, usage, config, __remote-workflow), then straight into the alphabetical run of plugin entries: agent-orchestra:adversarial-review, agent-orchestra:ai-first-documentation, agent-orchestra:audit-docs, ...
agent-orchestra:experience appears in that run at its plain alphabetical position, between agent-orchestra:engagement-record-emission and agent-orchestra:frame-credit-emission.
That placement is the tell. Hoisted commands are removed from the later buckets via the m set, so a command sitting in its alphabetical slot cannot also have been hoisted. Bucket p was empty.
Expected
agent-orchestra:experience, :design, :plan, :orchestrate and :review-github appear as the first five entries, above the built-ins, and are absent from the alphabetical run.
Likely locus
Since the engine-side preconditions all hold, my guess is that the desktop surface builds its own list rather than consuming this ordering function - but I can only see the bundled engine, not the app shell, so I can't confirm that half. If the ordering is applied identically in the terminal TUI, that would localize it quickly.
Why this is worth fixing rather than routing around
There is no user-side lever for it. ~/.claude/commands/*.md sorts above plugin entries, so short personal wrappers help, but commands that pin model: in frontmatter cannot be usefully wrapped. And there is no setting to trim the menu - I checked for disabledSkills / hiddenCommands / disabledCommands and equivalents; plugin enable/disable is whole-plugin only. The hoist is the mechanism that makes a large menu usable, and it already exists.
Related
- #80125 - a desktop user reporting this exact symptom ("arbitrary order - not alphabetical") and asking for alphabetical sorting as a feature. Same underlying cause, I think.
- #75044, #79157 - favorites/pinned-bar requests, which overlap in motivation.
- #59023 - "weighted command history based on execution frequency", closed not-planned as stale, though the mechanism shipped.
- #32859 - "Sort slash commands by usage frequency", auto-closed as a duplicate of #23668, which was itself closed as a duplicate.