router-for-me/CLIProxyAPI · error
plugin function table is incomplete
Error message
plugin function table is incomplete
What it means
On Windows, after init succeeds and the ABI matches, the plugin must provide at least the call and freeBuffer function pointers in the API struct. If either is 0 (NULL), the function table is incomplete and the host rejects the plugin, because it could never invoke or clean up plugin calls.
Source
Thrown at internal/pluginhost/loader_windows.go:113
hostAPI: &windowsHostAPI{
abiVersion: pluginHostABIVersion,
hostCtx: uintptr(unsafe.Pointer(hostCtx)),
call: windowsHostCallCallback,
freeBuffer: windowsHostFreeCallback,
},
}
rc, _, errCall := proc.Call(uintptr(unsafe.Pointer(client.hostAPI)), uintptr(unsafe.Pointer(&client.api)))
if rc != 0 {
client.closeAfterOpenFailure()
return nil, fmt.Errorf("cliproxy_plugin_init returned %d: %v", rc, errCall)
}
if client.api.abiVersion != pluginHostABIVersion {
client.closeAfterOpenFailure()
return nil, fmt.Errorf("plugin ABI version %d is not supported", client.api.abiVersion)
}
if client.api.call == 0 || client.api.freeBuffer == 0 {
client.closeAfterOpenFailure()
return nil, fmt.Errorf("plugin function table is incomplete")
}
return client, nil
}
func shadowCopyPlugin(file pluginFile) (string, error) {
dir, errDir := shadowPluginDir()
if errDir != nil {
return "", errDir
}
shadowPluginCleanupOnce.Do(func() {
removeStaleShadowPlugins(dir)
})
return shadowCopyPluginToDir(file, dir)
}
func shadowCopyPluginToDir(file pluginFile, dir string) (string, error) {
source := filepath.Clean(file.Path)
tmp, errTemp := os.CreateTemp(dir, shadowPluginTempPrefix+file.ID+"-*"+filepath.Ext(source))View on GitHub (pinned to 78f0c4079e)
Solutions
- Ensure the plugin DLL exports and registers both the call handler and the free_buffer handler in the API struct during init
- Compare the plugin's export names with the SDK's expected export names using dumpbin /exports or objdump -p
- Build the plugin from the official SDK scaffold so all required exports are wired up
Defensive patterns
Strategy: validation
Try / catch
if err != nil && strings.Contains(err.Error(), "function table is incomplete") {
// plugin is misbuilt; skip it rather than retrying
disablePlugin(id)
} Prevention
- Build plugins from the official SDK scaffold
- Check exports with dumpbin /exports or objdump -p before shipping
- Add a load test to the plugin's CI
When it happens
Trigger: Plugin's init fills abiVersion but leaves call or freeBuffer unset (NULL exports, misnamed exports, or a partially implemented plugin); a plugin built from a template that only implements lifecycle hooks.
Common situations: Early-stage plugin development where the RPC entry points are not yet exported; export name typos (e.g. CliproxyCall vs cliproxy_call) resulting in NULL pointers; linker stripping exports due to dllexport attributes missing.
Related errors
- cliproxy_plugin_init returned %d: %v
- plugin ABI version %d is not supported
- remove stale shadow plugin: %w
- plugin client is closed
- plugin call %s returned %d: %s
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/b1849f902f203e2d.
Report an issue: GitHub.