{"record":{"id":"b1849f902f203e2d","repo":"router-for-me/CLIProxyAPI","slug":"plugin-function-table-is-incomplete-b1849f","errorCode":null,"errorMessage":"plugin function table is incomplete","messagePattern":"plugin function table is incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/loader_windows.go","lineNumber":113,"sourceCode":"\t\thostAPI: &windowsHostAPI{\n\t\t\tabiVersion: pluginHostABIVersion,\n\t\t\thostCtx:    uintptr(unsafe.Pointer(hostCtx)),\n\t\t\tcall:       windowsHostCallCallback,\n\t\t\tfreeBuffer: windowsHostFreeCallback,\n\t\t},\n\t}\n\trc, _, errCall := proc.Call(uintptr(unsafe.Pointer(client.hostAPI)), uintptr(unsafe.Pointer(&client.api)))\n\tif rc != 0 {\n\t\tclient.closeAfterOpenFailure()\n\t\treturn nil, fmt.Errorf(\"cliproxy_plugin_init returned %d: %v\", rc, errCall)\n\t}\n\tif client.api.abiVersion != pluginHostABIVersion {\n\t\tclient.closeAfterOpenFailure()\n\t\treturn nil, fmt.Errorf(\"plugin ABI version %d is not supported\", client.api.abiVersion)\n\t}\n\tif client.api.call == 0 || client.api.freeBuffer == 0 {\n\t\tclient.closeAfterOpenFailure()\n\t\treturn nil, fmt.Errorf(\"plugin function table is incomplete\")\n\t}\n\treturn client, nil\n}\n\nfunc shadowCopyPlugin(file pluginFile) (string, error) {\n\tdir, errDir := shadowPluginDir()\n\tif errDir != nil {\n\t\treturn \"\", errDir\n\t}\n\tshadowPluginCleanupOnce.Do(func() {\n\t\tremoveStaleShadowPlugins(dir)\n\t})\n\treturn shadowCopyPluginToDir(file, dir)\n}\n\nfunc shadowCopyPluginToDir(file pluginFile, dir string) (string, error) {\n\tsource := filepath.Clean(file.Path)\n\ttmp, errTemp := os.CreateTemp(dir, shadowPluginTempPrefix+file.ID+\"-*\"+filepath.Ext(source))","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/loader_windows.go#L95-L131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"function table is incomplete\") {\n    // plugin is misbuilt; skip it rather than retrying\n    disablePlugin(id)\n}","preventionTips":["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"],"tags":["plugin","windows","dll","contract","dynamic-library"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}