router-for-me/CLIProxyAPI · error

plugin executor %s count tokens panic: %v

Error message

plugin executor %s count tokens panic: %v

What it means

The plugin's CountTokens implementation panicked; the adapter's deferred recover() converted the panic to an error, zeroed the response, and fused the plugin via fusePlugin so subsequent calls fail fast.

Source

Thrown at internal/pluginhost/adapters_executors.go:769

	if next == nil {
		return nil, fmt.Errorf("plugin executor %s refresh returned invalid auth data", a.Identifier())
	}
	if auth != nil {
		next.CreatedAt = auth.CreatedAt
		next.UpdatedAt = auth.UpdatedAt
	}
	return next, nil
}

func (a *executorAdapter) CountTokens(ctx context.Context, auth *coreauth.Auth, req coreexecutor.Request, opts coreexecutor.Options) (resp coreexecutor.Response, err error) {
	if a == nil || a.executor == nil || a.host.isPluginFused(a.pluginID) || !a.host.pluginIdentityCurrent(a.pluginID, a.path, a.version) {
		return coreexecutor.Response{}, fmt.Errorf("plugin executor %s is unavailable", a.Identifier())
	}
	defer func() {
		if recovered := recover(); recovered != nil {
			a.host.fusePlugin(a.pluginID, "Executor.CountTokens", recovered)
			resp = coreexecutor.Response{}
			err = fmt.Errorf("plugin executor %s count tokens panic: %v", a.Identifier(), recovered)
		}
	}()

	prepared, errPrepare := a.prepareExecutorCall(req, opts)
	if errPrepare != nil {
		return coreexecutor.Response{}, errPrepare
	}
	pluginResp, errCountTokens := a.executor.CountTokens(ctx, buildExecutorRequest(a.host, a.provider, auth, prepared.req, prepared.opts))
	if errCountTokens != nil {
		return coreexecutor.Response{}, errCountTokens
	}
	return coreexecutor.Response{
		Payload:  a.translateExecutorResponse(ctx, prepared, pluginResp.Payload, false, nil),
		Metadata: cloneAnyMap(pluginResp.Metadata),
		Headers:  cloneHeader(pluginResp.Headers),
	}, nil
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Read the recovered panic value in the host log to locate the failing dereference in the plugin
  2. Make the plugin's CountTokens defensive: validate model/payload before use and return an error instead of panicking
  3. Reinstall the fixed plugin and reload so the fuse clears
  4. Ensure the models the plugin advertises match the models its tokenizer actually supports
Defensive patterns

Strategy: try-catch

Try / catch

resp, err := adapter.CountTokens(ctx, auth, req, opts)
if err != nil {
    if strings.Contains(err.Error(), "count tokens panic") {
        return fallbackCountTokens(req) // plugin fused; use local estimator
    }
    return coreexecutor.Response{}, err
}

Prevention

When it happens

Trigger: Calling CountTokens where the plugin's CountTokens dereferences something invalid — e.g. unexpected model name, missing tokenizer entry, nil request payload fields after prepareExecutorCall.

Common situations: Plugin tokenizer lookup table lacks the requested model; plugin assumes a payload schema the host no longer sends; plugin built against an older plugin API.

Related errors


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