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

  1. Ensure the plugin DLL exports and registers both the call handler and the free_buffer handler in the API struct during init
  2. Compare the plugin's export names with the SDK's expected export names using dumpbin /exports or objdump -p
  3. 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

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


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