fix(skill): invalidate skill cache when SKILL.md files change on disk#19136
fix(skill): invalidate skill cache when SKILL.md files change on disk#19136jwm4 wants to merge 1 commit intoanomalyco:devfrom
Conversation
Skills were loaded once at startup and cached permanently — editing a SKILL.md file while opencode was running had no effect until restart. Add cache invalidation triggered by the existing FileWatcher infrastructure: - Skill cache clears when a SKILL.md file changes (via Bus subscription) - Command cache rebuilds when skills reload (via Skill.Event.Invalidated) - Expose Skill.invalidate() for programmatic use Fixes anomalyco#19050 ## Summary - Fix skill cache not invalidating when SKILL.md files are edited on disk - Add `Skill.Event.Invalidated` bus event for cross-service coordination - Invalidate Command cache when skills reload so slash commands reflect changes ## Test plan - [x] 4 new regression tests (edit, get, add, delete) - [x] All 11 existing skill tests pass - [x] Full test suite: 1468 pass, 0 fail - [x] Type checking passes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
jwm4
left a comment
There was a problem hiding this comment.
Manual testing feedback
The cache invalidation plumbing works correctly — Skill.invalidate() clears the cache and the next Skill.all()/Skill.get() call re-reads from disk. The automated tests all pass.
However, there's a significant gap in the end-to-end flow: the fix only works when OPENCODE_EXPERIMENTAL_FILEWATCHER=true is set, which most users won't have enabled.
The PR subscribes to FileWatcher.Event.Updated to detect SKILL.md changes, but FileWatcher only watches Instance.directory when the experimental flag is on (see src/file/watcher.ts:123). Without it, only the .git directory is watched — so FileWatcher.Event.Updated never fires for SKILL.md changes, and the cache is never invalidated.
This means the original issue (#19050) remains unfixed for the default configuration.
Additional note
Even with the experimental flag enabled, the FileWatcher only watches Instance.directory (the CWD). In monorepo setups, skills loaded from parent .opencode/ directories (found via Filesystem.up) won't trigger invalidation when edited, since the watcher doesn't cover those parent directories.
Suggestions
- Consider an approach that doesn't depend on
OPENCODE_EXPERIMENTAL_FILEWATCHER— e.g., a dedicated watcher for skill directories, or polling skill files for changes. - If sticking with FileWatcher, the watcher scope should cover all directories returned by
Config.directories()and the external skill dirs, not justInstance.directory.
Issue for this PR
Closes #19050
Type of change
What does this PR do?
Skills are loaded once by
ensure()inskill/index.tsand the result is cached as a resolved promise. Editing aSKILL.mdfile while opencode is running has no effect until restart because nothing clears that cache.This fix adds an
invalidate()function that resets the cached promise. It's triggered automatically when the existingFileWatcherdetects a change to anySKILL.mdfile. TheCommandservice also subscribes to a newSkill.Event.Invalidatedbus event so slash commands pick up the fresh content.Two source files changed, one new test file added:
packages/opencode/src/skill/index.ts— cache invalidation + file watcher subscriptionpackages/opencode/src/command/index.ts— subscribe to skill invalidation to rebuild commandspackages/opencode/test/skill/skill-stale-cache.test.ts— 4 regression testsHow did you verify your code works?
invalidate(), then verifySkill.list()returns updated content (edit, add, delete scenarios)bun turbo typecheck)Screenshots / recordings
N/A — no UI changes.
Checklist