router-for-me/CLIProxyAPI · error

plugin executor %s is unavailable

Error message

plugin executor %s is unavailable

What it means

The requested plugin record exists and is active, but isPluginFused reports it as fused — the host disabled it after repeated panics in its capability calls. All executor routing to it is refused with this error naming the plugin ID.

Source

Thrown at internal/pluginhost/executor_route.go:125

		return coreexecutor.Response{}, errAdapter
	}
	return adapter.CountTokens(ctx, (*coreauth.Auth)(nil), req, opts)
}

func (h *Host) executorAdapterForPlugin(pluginID string) (*executorAdapter, error) {
	if h == nil {
		return nil, fmt.Errorf("plugin host is unavailable")
	}
	pluginID = strings.TrimSpace(pluginID)
	if pluginID == "" {
		return nil, fmt.Errorf("target executor plugin id is required")
	}
	for _, record := range h.activeRecords() {
		if record.id != pluginID {
			continue
		}
		if h.isPluginFused(record.id) {
			return nil, fmt.Errorf("plugin executor %s is unavailable", pluginID)
		}
		executor := record.plugin.Capabilities.Executor
		if executor == nil {
			return nil, fmt.Errorf("plugin %s does not declare an executor", pluginID)
		}
		provider, okProvider := h.executorProvider(record, executor)
		if !okProvider {
			return nil, fmt.Errorf("plugin executor %s has no provider identifier", pluginID)
		}
		registration := newExecutorAdapterRegistration(h, record, provider, executor)
		return registration.adapter, nil
	}
	return nil, fmt.Errorf("plugin executor %s not found", pluginID)
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Search logs for fusePlugin entries mentioning this plugin ID to find the originating panic and capability.
  2. Fix or update the plugin (see the panic error's guidance), then restart/reload the host to clear the fuse.
  3. If panics are load-triggered, capture the plugin's stack trace by running it standalone under the same inputs.
  4. Consider removing the plugin from config until fixed so routing fails fast at config time rather than per-request.
Defensive patterns

Strategy: fallback

Validate before calling

if host.IsPluginFused(pluginID) {
    return fmt.Errorf("plugin %s is disabled after repeated panics; see fuse logs", pluginID)
}

Type guard

func executorUsable(h *pluginhost.Host, pluginID string) bool {
    for _, rec := range h.ActiveRecords() {
        if rec.ID == pluginID {
            return !h.IsPluginFused(pluginID) && rec.Plugin.Capabilities.Executor != nil
        }
    }
    return false
}

Try / catch

resp, err := host.ExecutePluginExecutor(ctx, pluginID, req, opts)
if err != nil && strings.Contains(err.Error(), "is unavailable") {
    // fused plugin: permanent until restart; route to fallback provider/executor
    return routeToFallback(model, req, opts)
}

Prevention

When it happens

Trigger: Requesting execution/count-tokens on a plugin whose executor (or other capability) panicked enough times to trip the fuse; the fuse log entries (from fusePlugin) name the capability and recovered value that caused it.

Common situations: A buggy plugin build that panics under load; plugin incompatible with the current host API causing repeated panics; leftover fuse state after an earlier incident in a long-running process.

Related errors


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