[Bug]: Project skills drop out of the $ picker for seconds after 16 other folders were scanned#22429
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Related but different: #21181 (closed as an enhancement) is about the payload size of these per-folder copies. #14801 is about a scan that runs before the skills exist. #16866 is about failed or overlapping scans. This report is about what the client shows once a folder's copy has been evicted by the 16-entry cap.
Area
apps/server
Steps to reproduce
- Use one Claude provider instance with a repository that has project skills in
.claude/skills(here, a monorepo with about 40 tracked skills; each thread runs in its own worktree). - Open a thread in worktree A and type
$. The project skills are listed. - Open threads in 16 other worktrees of the same repository, one after another, so each composer scans its folder.
- Go back to the thread in worktree A within 5 minutes and type
$.
Expected behavior
The picker still lists worktree A's project skills, as it does for any folder scanned in the last 5 minutes, or at worst keeps showing the previous list while it rescans.
Actual behavior
For a few seconds the picker lists only the user skills from ~/.claude/skills. Then the project skills come back once a new scan of worktree A finishes. This repeats every time the user returns to a folder after 16 other folders were scanned. It looks like the project skills "sometimes" go missing.
Cause, read from main @ be5a5cd0:
upsertProviderWorkspaceSnapshotkeeps only the last 16 workspace snapshots per provider instance (MAX_WORKSPACE_SNAPSHOTS_PER_PROVIDER,apps/server/src/provider/ProviderRegistry.ts:118,.slice(-…)at:151). The oldest-scanned folder is dropped.- With no snapshot for its folder,
resolveProviderSkillsForCwdfalls back to the provider's machine-levelskills(packages/client-runtime/src/providerSkills.ts). For Claude these are user-scope skills only, because the machine snapshot is not probed in a project. - The composer notices the missing snapshot and asks for a rescan, but the Claude workspace scan publishes nothing until
probeClaudeCapabilitieshas spawned Claude in that folder (probeClaudeWorkspaceSnapshot,apps/server/src/provider/ClaudeProvider.ts).discoverClaudeSkillsitself takes milliseconds. - When a folder's snapshot has merely expired (older than 5 minutes) but is not evicted, the stale list stays visible during the rescan, so there is no gap. The gap appears only after eviction.
Observed on my machine (desktop nightly):
- Since the server started about 2 days ago, threads on the
claudeAgentinstance used 26 distinct worktree folders, 23 of them on the last day. That is more than the 16-entry cap, so eviction must have happened. I could not inspect the server's in-memory list directly, so the eviction itself is inferred from the code and these counts. - In
server.trace.ndjson,probeClaudeWorkspaceSnapshotspans took 0.3–4.5 s, while theirdiscoverClaudeSkillschildren took 0–200 ms. The capability probe timeout is 25 s.
Reproduced in a unit test against main: after upserting snapshots for 17 folders, the first folder has no snapshot, and resolving its skills the way the client does returns only the machine-level user skill.
Note on possible fixes: keeping the cap but evicting by last read would not cover the single-window case. The composer does not call the server while its snapshot is current, so the server never sees a read. In this repro, worktree A really is the least recently used entry when it is evicted. Options I can see, which need a maintainer decision because they trade off against the payload concern in #21181:
- Publish the
discoverClaudeSkillsresult before the capability probe finishes, withslashCommandsPending: true. This shrinks the gap from seconds to milliseconds without changing the cap. - Let the client keep showing the last list it had for a folder while that folder is being rescanned after eviction.
- Raise or remove the cap once identical copies are cheap (the marker idea in #21181).
Impact
Minor bug or occasional failure
Version or commit
Desktop 0.1.1-nightly.20261018.3302; the cap and fallback are unchanged on main @ be5a5cd0.
Environment
macOS (Apple Silicon), T3 Code desktop nightly, Claude Code 2.1.304, Claude provider. Codex threads hit the same cap, but the effect is hidden on my machine because the same skills are also installed at user level in ~/.agents/skills.
Workaround
Wait a few seconds after opening the thread, or type the full $name and send it: the server discovers the thread's skills again when it sends the turn, so the skill still runs. Restart agent session from cmd+K forces a fresh rescan.
Investigated with Claude Opus 5.5 through Claude Code.