router-for-me/CLIProxyAPI · error · ErrLoadedPluginLocked
plugin_update_requires_restart
plugin_update_requires_restart
Error message
loaded plugin library cannot be overwritten while the server is running
What it means
Returned by pluginstore Install when an install would overwrite a plugin dynamic library that the running process currently has loaded, which Windows forbids (loaded DLLs are locked). The sentinel ErrLoadedPluginLocked carries the plugin_update_requires_restart code: the install must be retried after a restart unloads the library. On non-Windows platforms loaded libraries can be replaced on disk, so this primarily guards Windows deployments.
Source
Thrown at internal/pluginstore/install.go:35
log "github.com/sirupsen/logrus"
)
type InstallOptions struct {
PluginsDir string
GOOS string
GOARCH string
// PluginLoaded reports whether the plugin's dynamic library is currently
// loaded by the running host. Windows installs are rejected only when they
// would overwrite an existing target file while it returns true.
PluginLoaded func() bool
// BeforeWrite runs after the archive has been downloaded and verified, but
// before an existing target plugin file is replaced.
BeforeWrite func() error
}
// ErrLoadedPluginLocked is returned when an install would overwrite a plugin
// library that is loaded by the running process on Windows.
var ErrLoadedPluginLocked = errors.New("loaded plugin library cannot be overwritten while the server is running")
type InstallResult struct {
ID string `json:"id"`
Version string `json:"version"`
ReleaseTag string `json:"release_tag,omitempty"`
InstallType string `json:"install_type,omitempty"`
Path string `json:"path"`
Overwritten bool `json:"overwritten"`
Skipped bool `json:"skipped"`
}
func (c Client) Install(ctx context.Context, plugin Plugin, options InstallOptions) (InstallResult, error) {
if errValidate := ValidatePlugin(plugin); errValidate != nil {
return InstallResult{}, errValidate
}
options = normalizeInstallOptions(options)
if PluginInstallType(plugin) == InstallTypeDirect {
plugin.Version = normalizeVersion(plugin.Version)View on GitHub (pinned to 78f0c4079e)
Solutions
- Restart the server (or unload the plugin if a unload endpoint exists) and re-run the same install request — the archive and ID stay valid
- If you must update without downtime on Windows, move the deployment to a non-Windows host or accept the documented restart requirement
- Check the InstallResult.Skipped/Overwritten fields and the plugin_update_requires_restart code to distinguish this from other install failures
Defensive patterns
Strategy: fallback
Try / catch
res, err := pluginClient.Install(ctx, plugin, opts)
if err != nil {
if errors.Is(err, pluginstore.ErrLoadedPluginLocked) {
// Windows: library file is locked; schedule reinstall after restart
schedulePostRestartInstall(plugin)
return reconciled(err)
}
return err
} Prevention
- On Windows, treat plugin updates as restart-pending operations: queue them and re-apply on boot
- Check InstallResult codes for plugin_update_requires_restart instead of retrying in a loop
- Prefer non-Windows hosts for hot-reload plugin deployments
When it happens
Trigger: POST to the plugin install/update management endpoint for a plugin whose PluginLoaded() returns true, on Windows, where the target .dll file still exists and would be overwritten by the extracted archive.
Common situations: Hot-updating a plugin through the management API on a Windows host; CI or operators scripting plugin updates against a live server.
Related errors
- remove old plugin file: %w
- plugin_install_failed
- zip contains multiple target dynamic libraries
- zip does not contain %s
- zip entry %s uses backslash path separators
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/468bb751856c6e0f.
Report an issue: GitHub.