feat: hot-reload skills, commands, agents and config on file change - #43538
mccaffrey-jonathan wants to merge 4 commits into
Conversation
Behind OPENCODE_EXPERIMENTAL_HOT_RELOAD. The filesystem watcher gains subscriptions for config directories (global config dir and .opencode dirs) so changes there emit events even when the project watcher is off. A new HotReload service listens for those events, filters them to config-relevant paths, debounces, then reloads the instance through InstanceStore.reload and drops the v2 location-service layer, so both stacks reread disk state. Clients already re-sync on the existing server.instance.disposed event; the TUI additionally refreshes its v2 agent/command/skill stores. Plugin install artifacts (package.json, lockfiles) inside config dirs are ignored to avoid reload loops. Closes anomalyco#8751 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTuWSW35XoRbPYixZr8oKQ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTuWSW35XoRbPYixZr8oKQ
Persistent layer-level listener survives failed re-boots; whitelist relevance filter (config subdirs, skill dirs, exact config file paths) instead of blacklist, so plan saves and fixture opencode.json files no longer trigger reloads; reload forks are supervised by the layer scope; reload passes only the directory so project identity is re-derived and skips when the instance was disposed; location-service invalidation goes through a registry covering every built map and workspace-scoped refs; config-dir watches honor watcher.ignore; TUI refreshes all location data with the event location. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTuWSW35XoRbPYixZr8oKQ
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: Based on my search results, I found a potentially related PR: Related PR:
Also note:
The most likely duplicate or related work is PR #43458, which already implements a reload command. You should verify if this PR #43538 is addressing a different aspect of reloading or if it duplicates that functionality. |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
- The debounce was a leading-edge lock held for the whole reload: the first event armed it and everything arriving during the 200ms wait *and* the instance rebuild was discarded with no re-check, so an edit made while a reload ran never loaded. Replaced with a trailing-edge debounce plus a dirty flag the driver re-checks after each reload. Extracted as schedule/settle so the sequence is unit tested. - location-services registered every LayerMap in module state and never released it, and the ref index grew forever even after LayerMap evicted the entry. Registration is now acquireRelease-scoped to the layer, and refs are indexed by directory and dropped on invalidate. Also removes cross-test contamination: every test that built a map left a live entry behind. - Treat opencode.json[c] at every level between the instance directory and the worktree root as config - ConfigPaths.files loads all of them - and honour OPENCODE_CONFIG when it points inside a watched config directory. - Carry the project through the hot-reload reload so the emitted server.instance.disposed event is stamped like every other disposal. - Log refresh failures in the TUI instead of swallowing them; a silent rejection leaves exactly the stale skill list hot reload exists to replace. - Document that a config directory must exist at startup to be watched, and that a reload interrupts an in-flight turn.
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Issue for this PR
Closes #8751
Type of change
What does this PR do?
Opt-in hot reload behind
OPENCODE_EXPERIMENTAL_HOT_RELOAD=true..opencodedirs from config entries), so changes there emitfile.watcher.updatedeven when the whole-project watcher is off.HotReloadservice, armed byInstanceStoreafter each boot, listens for those events, filters to config-relevant paths (files under config/skill directories,opencode.json[c]anywhere), debounces 200ms, then reloads through the existingInstanceStore.reloadand invalidates the v2 location-service layer so both stacks reread disk state.server.instance.disposed; the TUI additionally refreshes its v2 agent/command/skill stores on that event.This is also one answer to the
QUESTION(Dax)comment inpackages/core/src/skill.ts: the reload policy here is "drop the location layer on watch events" rather than per-cache invalidation.Known limitations: file events landing in the short window between dispose and re-arm are missed; external skill directories are watched only once discovered (each reload re-discovers); the watcher arms when v2 location services build, which every TUI/desktop/web client does immediately.
How did you verify your code works?
End to end on Windows with
opencode serveand the flag set, while the server was running:/skilland/api/skillwithout restart.opencode/command/hotcmd.md— appears in/commandserver.instance.disposedon the/eventSSE stream on each reloadTests:
Built and tested with Claude assistance; I reviewed and verified the changes.
Checklist