Files
orca/src
Neil f56c37b470 fix(skills): stop rescanning on unrelated store writes and bound the discovery cache (#7670)
Two fixes to installed-skill discovery in the renderer.

**Stops one discovery IPC per unrelated store write.** `refresh`'s `useCallback` depended on the `discoveryTarget` *object*, which callers rebuild inside a store-backed `useMemo`. Any unrelated store write handed the hook a fresh identity, recreated `refresh`, and re-fired `useEffect(() => void refresh(false), [refresh])`. On a cache hit that is only extra renders — but on a *rejecting* scan nothing is cached and the pending entry is cleared, so it becomes one discovery IPC per store write, indefinitely. That is the remote-runtime-unreachable and SSH case. Measured 6 scans where 1 was correct.

This dep also exists on `main`, so this repairs a pre-existing bug rather than only one introduced here. Fixed with a render-phase `useState` latch keyed on the already-present `discoveryTargetKey` string — deliberately not a `useMemo` (React documents those as discardable, so a discarded one silently restores the regression while the test stays green) and not a ref (react-doctor correctly flags a render-phase ref mutation).

**Bounds the discovery cache.** The module-level maps were unbounded, cleared only wholesale by `notifyInstalledAgentSkillsChanged`. Now a 256-entry LRU keyed by `getRuntimeScopedSkillDiscoveryKey`, with a `discoveryGeneration` guard and a read/peek split so only the unforced cache-serving path promotes recency.

This matters more after #6887 than before it. Local keys are bounded by project count (~47 KB/entry, ~240 KB for 5 repos), but once remote scans key on `runtime:<environmentId>`, ephemeral VMs mint `orca-${randomUUID()}` per start (`ephemeral-vm-runtime-service.ts` + `ephemeral-vm-recipe-runner.ts`), so every VM start creates a permanently-retained entry — ~18.8 KB each, ~1.8 MB after ~100 starts, and it does not stop.

Scoped down during review: an `executionHostId` change touching 8 production files was removed as a second feature (host-awareness landed in #6887 instead), taking this from 17 files to 6. New tests pin that the cap keys off the runtime-scoped key, that two environments never share an entry, and that one environment collapses to a single entry across differing client target shapes.

Known gap, not addressed here: cache entries are not evicted when a runtime environment is removed, so a removed environment's skill list is retained until LRU pressure or an install notification clears it. That needs a store subscription to `runtimeEnvironments` removals and is filed separately.

Co-authored-by: nwparker <4138956+nwparker@users.noreply.github.com>
2026-07-29 16:30:07 -07:00
..