{"record":{"id":"21097b61aab7f25d","repo":"router-for-me/CLIProxyAPI","slug":"plugin-executor-s-count-tokens-panic-v","errorCode":null,"errorMessage":"plugin executor %s count tokens panic: %v","messagePattern":"plugin executor (.+?) count tokens panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/adapters_executors.go","lineNumber":769,"sourceCode":"\tif next == nil {\n\t\treturn nil, fmt.Errorf(\"plugin executor %s refresh returned invalid auth data\", a.Identifier())\n\t}\n\tif auth != nil {\n\t\tnext.CreatedAt = auth.CreatedAt\n\t\tnext.UpdatedAt = auth.UpdatedAt\n\t}\n\treturn next, nil\n}\n\nfunc (a *executorAdapter) CountTokens(ctx context.Context, auth *coreauth.Auth, req coreexecutor.Request, opts coreexecutor.Options) (resp coreexecutor.Response, err error) {\n\tif a == nil || a.executor == nil || a.host.isPluginFused(a.pluginID) || !a.host.pluginIdentityCurrent(a.pluginID, a.path, a.version) {\n\t\treturn coreexecutor.Response{}, fmt.Errorf(\"plugin executor %s is unavailable\", a.Identifier())\n\t}\n\tdefer func() {\n\t\tif recovered := recover(); recovered != nil {\n\t\t\ta.host.fusePlugin(a.pluginID, \"Executor.CountTokens\", recovered)\n\t\t\tresp = coreexecutor.Response{}\n\t\t\terr = fmt.Errorf(\"plugin executor %s count tokens panic: %v\", a.Identifier(), recovered)\n\t\t}\n\t}()\n\n\tprepared, errPrepare := a.prepareExecutorCall(req, opts)\n\tif errPrepare != nil {\n\t\treturn coreexecutor.Response{}, errPrepare\n\t}\n\tpluginResp, errCountTokens := a.executor.CountTokens(ctx, buildExecutorRequest(a.host, a.provider, auth, prepared.req, prepared.opts))\n\tif errCountTokens != nil {\n\t\treturn coreexecutor.Response{}, errCountTokens\n\t}\n\treturn coreexecutor.Response{\n\t\tPayload:  a.translateExecutorResponse(ctx, prepared, pluginResp.Payload, false, nil),\n\t\tMetadata: cloneAnyMap(pluginResp.Metadata),\n\t\tHeaders:  cloneHeader(pluginResp.Headers),\n\t}, nil\n}\n","sourceCodeStart":751,"sourceCodeEnd":787,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/adapters_executors.go#L751-L787","documentation":"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.","triggerScenarios":"Calling CountTokens where the plugin's CountTokens dereferences something invalid — e.g. unexpected model name, missing tokenizer entry, nil request payload fields after prepareExecutorCall.","commonSituations":"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.","solutions":["Read the recovered panic value in the host log to locate the failing dereference in the plugin","Make the plugin's CountTokens defensive: validate model/payload before use and return an error instead of panicking","Reinstall the fixed plugin and reload so the fuse clears","Ensure the models the plugin advertises match the models its tokenizer actually supports"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := adapter.CountTokens(ctx, auth, req, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"count tokens panic\") {\n        return fallbackCountTokens(req) // plugin fused; use local estimator\n    }\n    return coreexecutor.Response{}, err\n}","preventionTips":["Test plugin tokenizers against every model the plugin advertises","Return errors from plugins instead of panicking; wrap plugin code in its own recover during development","Alert on 'count tokens panic' and remove the model from routing until fixed"],"tags":["plugin","panic","token-count","circuit-breaker","recover"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}