Skip to content

feat: hot-reload skills, commands, agents and config on file change - #43538

Open
mccaffrey-jonathan wants to merge 4 commits into
anomalyco:devfrom
mccaffrey-jonathan:hot-reload
Open

feat: hot-reload skills, commands, agents and config on file change#43538
mccaffrey-jonathan wants to merge 4 commits into
anomalyco:devfrom
mccaffrey-jonathan:hot-reload

Conversation

@mccaffrey-jonathan

@mccaffrey-jonathan mccaffrey-jonathan commented Aug 19, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #8751

Type of change

  • New feature

What does this PR do?

Opt-in hot reload behind OPENCODE_EXPERIMENTAL_HOT_RELOAD=true.

  • The filesystem watcher additionally subscribes config directories (global config dir and .opencode dirs from config entries), so changes there emit file.watcher.updated even when the whole-project watcher is off.
  • A new HotReload service, armed by InstanceStore after 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 existing InstanceStore.reload and invalidates the v2 location-service layer so both stacks reread disk state.
  • Clients already re-sync on server.instance.disposed; the TUI additionally refreshes its v2 agent/command/skill stores on that event.
  • Plugin install artifacts (package.json, lockfiles) inside config dirs are ignored to avoid reload loops.

This is also one answer to the QUESTION(Dax) comment in packages/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 serve and the flag set, while the server was running:

  • added a new skill directory — appears in /skill and /api/skill without restart
  • edited a skill description — updated in the list
  • added .opencode/command/hotcmd.md — appears in /command
  • observed server.instance.disposed on the /event SSE stream on each reload
  • confirmed a single debounced reload per change, no reload loop

Tests:

  • packages/core: new watcher test "watches config directories when hot reload is enabled" (flag on, non-git root — config-dir events fire, project files stay unwatched)
  • packages/opencode: unit tests for the relevance filter (config dirs, opencode.json anywhere, lockfile exclusions, prefix-sibling edge case)
  • typecheck clean in core/opencode/tui; packages/opencode config suite (191 tests) passes; the one failure in test/project/instance-bootstrap.test.ts reproduces identically on a clean dev checkout on this machine (environment-related, not from this change)

Built and tested with Claude assistance; I reviewed and verified the changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

mccaffrey-jonathan and others added 3 commits August 19, 2026 14:24
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
@github-actions

Copy link
Copy Markdown
Contributor

Hey! Your PR title Hot reload doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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.

@mccaffrey-jonathan mccaffrey-jonathan changed the title Hot reload feat: hot-reload skills, commands, agents and config on file change Aug 19, 2026
@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:title labels Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Hot-reload agents, skills and commands.

1 participant