router-for-me/CLIProxyAPI · error

plugin host is unavailable

Error message

plugin host is unavailable

What it means

executorAdapterForPlugin returns this when the receiver Host itself is nil. It is a programming/lifecycle error: caller invoked CountPluginExecutor/ExecutePluginExecutor-style routing on a nil *Host, typically because the plugin host was never initialized in this process or was already torn down and the pointer zeroed.

Solutions

  1. Ensure the plugin host is created and attached to the service before any request can be routed to a plugin executor.
  2. Guard call sites: skip plugin-executor routing when the host is nil rather than calling into it.
  3. If using official cmd/server, report a bug — this state should not be reachable there.

Example fix

// before
adapter, err := host.executorAdapterForPlugin(pluginID) // host may be nil

// after
if host == nil {
	return nil, fmt.Errorf("plugin routing requested but plugin host is not initialized")
}
adapter, err := host.executorAdapterForPlugin(pluginID)
Defensive patterns

Strategy: type-guard

Validate before calling

if host == nil {
    return fmt.Errorf("plugin executor routing skipped: plugin host not initialized")
}

Type guard

func pluginHostReady(h *pluginhost.Host) bool {
    return h != nil
}

Try / catch

// Go: defend with a nil-receiver check rather than catch
if h == nil {
    return coreexecutor.Response{}, fmt.Errorf("plugin host unavailable")
}
resp, err := h.CountPluginExecutor(ctx, pluginID, req, opts)

Prevention

When it happens

Trigger: Calling Host.CountPluginExecutor (or the execute route that uses executorAdapterForPlugin) on a nil host pointer: embedding code that skips plugin host setup but still routes model requests to plugin executors, or a code path that reads the host from an optional field that was never set.

Common situations: Custom embeddings that treat the plugin host as optional; races during shutdown where the host reference is cleared while a request is being routed; tests constructing partial service objects.

Related errors


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

Appendix: source

Thrown at internal/pluginhost/executor_route.go:114

	adapter, errAdapter := h.executorAdapterForPlugin(pluginID)
	if errAdapter != nil {
		return nil, errAdapter
	}
	return adapter.ExecuteStream(ctx, (*coreauth.Auth)(nil), req, opts)
}

// CountPluginExecutor executes a count-tokens request with the named plugin executor without changing the requested model.
func (h *Host) CountPluginExecutor(ctx context.Context, pluginID string, req coreexecutor.Request, opts coreexecutor.Options) (coreexecutor.Response, error) {
	adapter, errAdapter := h.executorAdapterForPlugin(pluginID)
	if errAdapter != nil {
		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 {

View on GitHub (pinned to 78f0c4079e)