[Bug]: Project skills drop out of the $ picker for seconds after 16 other folders were scanned#22429

opened12d agobyCcipriancaba

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

  1. 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).
  2. Open a thread in worktree A and type $. The project skills are listed.
  3. Open threads in 16 other worktrees of the same repository, one after another, so each composer scans its folder.
  4. 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:

  • upsertProviderWorkspaceSnapshot keeps 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, resolveProviderSkillsForCwd falls back to the provider's machine-level skills (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 probeClaudeCapabilities has spawned Claude in that folder (probeClaudeWorkspaceSnapshot, apps/server/src/provider/ClaudeProvider.ts). discoverClaudeSkills itself 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 claudeAgent instance 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, probeClaudeWorkspaceSnapshot spans took 0.3–4.5 s, while their discoverClaudeSkills children 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:

  1. Publish the discoverClaudeSkills result before the capability probe finishes, with slashCommandsPending: true. This shrinks the gap from seconds to milliseconds without changing the cap.
  2. Let the client keep showing the last list it had for a folder while that folder is being rescanned after eviction.
  3. 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.