gotify/server · error

NewGotifyPluginInstance signature mismatch, func(ctx plugin.

Error message

NewGotifyPluginInstance signature mismatch, func(ctx plugin.UserContext) plugin.Plugin expected, got %T

What it means

Wrap loads a Gotify plugin binary and checks that its exported NewGotifyPluginInstance symbol has the exact type func(plugin.UserContext) plugin.Plugin. If the type assertion fails because the plugin's constructor has a different signature (wrong API version, extra params, different return type), this error is returned.

Source

Thrown at plugin/compat/wrap.go:28

// Wrap wraps around a raw go plugin to provide typesafe access.
func Wrap(p *plugin.Plugin) (Plugin, error) {
	getInfoHandle, err := p.Lookup("GetGotifyPluginInfo")
	if err != nil {
		return nil, errors.New("missing GetGotifyPluginInfo symbol")
	}
	switch getInfoHandle := getInfoHandle.(type) {
	case func() papiv1.Info:
		v1 := PluginV1{}

		v1.Info = getInfoHandle()
		newInstanceHandle, err := p.Lookup("NewGotifyPluginInstance")
		if err != nil {
			return nil, errors.New("missing NewGotifyPluginInstance symbol")
		}
		constructor, ok := newInstanceHandle.(func(ctx papiv1.UserContext) papiv1.Plugin)
		if !ok {
			return nil, fmt.Errorf("NewGotifyPluginInstance signature mismatch, func(ctx plugin.UserContext) plugin.Plugin expected, got %T", newInstanceHandle)
		}
		v1.Constructor = constructor
		return v1, nil
	default:
		return nil, fmt.Errorf("unknown plugin version (unrecogninzed GetGotifyPluginInfo signature %T)", getInfoHandle)
	}
}

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Rebuild the plugin against the current gotify-android/plugin-api version
  2. Change the plugin's exported function to: func NewGotifyPluginInstance(ctx plugin.UserContext) plugin.Plugin
  3. Ensure the constructor returns the plugin.Plugin interface, not a concrete struct pointer
  4. Check GetGotifyPluginInfo's version marker matches the version branch Wrap expects

Example fix

// before
func NewGotifyPluginInstance(ctx plugin.UserContext, cfg Config) *MyPlugin { ... }
// after
func NewGotifyPluginInstance(ctx plugin.UserContext) plugin.Plugin { return &MyPlugin{} }
Defensive patterns

Strategy: type-guard

Validate before calling

sym, err := p.Lookup("NewGotifyPluginInstance")
if err != nil { panic("missing symbol") }
_, ok := sym.(func(papiv1.UserContext) papiv1.Plugin)
if !ok {
    fmt.Printf("wrong signature: %T\n", sym)
}

Type guard

func isValidConstructor(h interface{}) bool {
    _, ok := h.(func(papiv1.UserContext) papiv1.Plugin)
    return ok
}

Try / catch

compat, err := wrapper.Wrap(pluginPath)
if err != nil {
    if strings.Contains(err.Error(), "signature mismatch") {
        log.Printf("plugin %s built against wrong plugin-api: %v", pluginPath, err)
        return nil // skip plugin
    }
    return err
}

Prevention

When it happens

Trigger: loadPlugins or tests call Wrap on a compiled plugin whose NewGotifyPluginInstance does not type-assert to func(ctx papiv1.UserContext) papiv1.Plugin, e.g. the plugin was built against a different plugin-api version or returns a concrete type instead of plugin.Plugin.

Common situations: Plugin authors upgrade gotify but rebuild plugins against an old plugin-api, or hand-write the constructor with a wrong return type or extra parameters.

Related errors


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/0d7fa72b7c513e4e. Report an issue: GitHub.