anthropics / anthropics/claude-code

Slash menu: the top-5 most-used hoist never fires in the desktop app (frecency bucket returns empty)

Open
#81,239 0 comments 0 reactions 0 assignees View on GitHub
area:desktop area:tui
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### 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-orchestra` v3.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):

```js
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 `/.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:

```js
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

1. **`type === "prompt"`** - the plugin command/skill loader returns `{type: "prompt", name: e, ..., isHidden: !G}`, so plugin-contributed commands qualify.
2. **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)`), and `lNt` reads `skillUsage[name]`. Write key and read key are the same by construction.
3. **`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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the bundled engine's empty-filter menu-builder branch and compare its ordering with the desktop surface and terminal TUI. Reproduce the slash menu with the listed plugin usage data; done means the five highest-scoring prompt commands appear first and are removed from the alphabetical run.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.