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

  1. 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
  2. If you must update without downtime on Windows, move the deployment to a non-Windows host or accept the documented restart requirement
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/468bb751856c6e0f. Report an issue: GitHub.