Plugin hot reload on source change
Current-source status (verified 2026-09-28): implemented in the plugin server; off by default, enabled with NEKO_PLUGIN_HOT_RELOAD=true. This is a capability note, not a promise about a particular release train.
Summary
The plugin server can now watch plugin source directories and reload the affected plugin automatically when its code changes, removing the save → switch to plugin page → click Reload loop during development.
# PowerShell
$env:NEKO_PLUGIN_HOT_RELOAD = "true"; uv run python launcher.py
# bash
NEKO_PLUGIN_HOT_RELOAD=true uv run python launcher.pyWatched locations: every registered plugin's config directory under PLUGIN_CONFIG_ROOTS (built-in plugin/plugins/ and the user installation root) plus every development-mode registration's source_dir. Watched files: *.py and plugin.toml.
Semantics
- A reload is the existing
reload_plugintransaction (stop + start, i.e. the plugin subprocess is replaced). Auto reloads and manual button clicks take the same operation lock; the auto reload waits up to the debounce window for the lock, and onPluginOperationBusydefers by one debounce window and retries. - Changes must be quiet for
NEKO_PLUGIN_HOT_RELOAD_DEBOUNCEseconds (default 1.5) before the reload fires, so multi-file saves and in-progress writes do not reload half-written code. The scan runs everyNEKO_PLUGIN_HOT_RELOAD_INTERVALseconds (default 1.0). - Only running plugins are reloaded. A plugin the user stopped is never started by a file change; it picks up new code on its next manual start.
- Before stopping a healthy process, plugin-owned
.pyfiles are compiled andplugin.tomlis parsed. A syntactically broken edit skips the reload and keeps the running instance; the next change retries. Development-mode plugins additionally keep their existing full preflight insidereload_plugin. - Lifecycle events
plugin_hot_reload_triggered/_skipped/_failedare emitted for observability.
Implementation notes
plugin/server/application/plugins/hot_reload_service.py: stdlib-only polling watcher (mtime_ns + size signatures). No new dependencies; works on Windows / macOS / Linux alike.- Started at the end of
ServerLifecycleService.startup()and stopped at the top of_shutdown_internal(), before any plugin host is torn down, so an auto reload cannot race the shutdown. A shutdown latch additionally rejects plugin starts once teardown begins, so an in-flight reload can no longer register a host nobody will stop. - New settings (also exported through the admin API allowlist):
PLUGIN_HOT_RELOAD,PLUGIN_HOT_RELOAD_INTERVAL,PLUGIN_HOT_RELOAD_DEBOUNCE.
Testing
plugin/tests/unit/server/test_plugin_hot_reload_service.py covers: change detection with debounce, first-scan baselining, syntax-error and broken-manifest protection, no auto-start of stopped plugins (including the in-lock recheck), busy retry under real lock contention, the shutdown latch, idempotent stop, and restart rebaselining.
