router-for-me/CLIProxyAPI · error
plugin call %s returned %d: %s
Error message
plugin call %s returned %d: %s
What it means
The native plugin entry point (invoked via cgo cliproxy_call_plugin) returned a nonzero return code, and the response body was not a recognized plugin error envelope. The message embeds the RPC method name, the numeric rc, and whatever bytes the plugin wrote to the response buffer. This is the generic failure path for unix dynamic-library plugins.
Source
Thrown at internal/pluginhost/loader_unix.go:197
var cRequest unsafe.Pointer
if len(request) > 0 {
cRequest = C.CBytes(request)
defer C.free(cRequest)
}
var response C.cliproxy_buffer
rc := C.cliproxy_call_plugin(c.api.call, cMethod, (*C.uint8_t)(cRequest), C.size_t(len(request)), &response)
var out []byte
if response.ptr != nil && response.len > 0 {
out = C.GoBytes(response.ptr, C.int(response.len))
}
if response.ptr != nil {
C.cliproxy_free_plugin_buffer(c.api.free_buffer, response.ptr, response.len)
}
if rc != 0 {
if isPluginErrorEnvelope(out) {
return out, nil
}
return nil, fmt.Errorf("plugin call %s returned %d: %s", method, int(rc), string(out))
}
return out, nil
}
func (c *dynamicLibraryClient) Shutdown() {
if c == nil {
return
}
if c.api.shutdown != nil {
C.cliproxy_shutdown_plugin(c.api.shutdown)
c.api.shutdown = nil
}
if c.hostCtx != nil {
id := uintptr(*(*C.uintptr_t)(c.hostCtx))
hostCallbackEntries.Delete(id)
C.free(c.hostCtx)
c.hostCtx = nil
}View on GitHub (pinned to 78f0c4079e)
Solutions
- Check the plugin's own logs/stderr for the real cause; the %s payload usually carries the plugin's error text
- Verify the method name string matches exactly what the plugin registers
- Rebuild the plugin against the same pluginhost SDK/ABI version as the host binary
- If you author the plugin, return errors as the SDK's error envelope so the host surfaces them as structured responses instead of this opaque failure
Example fix
// before
resp, err := client.Call(ctx, "transform.Request", body)
if err != nil { log.Fatal(err) }
// after
resp, err := client.Call(ctx, "transform.Request", body)
if err != nil {
log.Errorf("plugin rpc failed (method=%s): %v", "transform.Request", err)
return fallbackResponse // degrade gracefully instead of crashing
} Defensive patterns
Strategy: fallback
Try / catch
out, err := client.Call(ctx, method, body)
if err != nil {
log.WithError(err).WithField("method", method).Error("plugin rpc failed")
return nil, err // or serve degraded response Prevention
- Pin plugin builds to the same SDK commit as the host
- Log the method name with every plugin call so the %s in this error maps to a code path
- In plugin code, always serialize errors via the SDK error envelope
When it happens
Trigger: Plugin method implementation returns an error without serializing it as a plugin error envelope; calling a method name the plugin does not implement; plugin crashes or corrupts its own state inside call(); ABI drift where the plugin misinterprets arguments.
Common situations: Plugin built against an older SDK version that formats errors differently; method name typo in the host-side dispatch (e.g. camelCase vs snake_case); plugin panicking across the C boundary and returning garbage; plugin out-of-memory returning rc != 0 with empty output.
Related errors
- plugin client is closed
- standard dynamic library plugin loading requires cgo on this
- plugin call %s returned %d: %s
- decode host auth list request: %w
- decode host auth get request: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/ba91b44b76d6a98d.
Report an issue: GitHub.