siyuan-note/siyuan · error
invalid plugin service protocol variants
Error message
invalid plugin service protocol variants
What it means
The PluginService protocol declaration attached to a plugin service endpoint must be byte-for-byte equal (via reflect.DeepEqual) to the canonical definition returned by PluginServiceOptions(). Because the variant list, admission statuses, WebSocket frame names, and SSE schema define the wire contract published in the bundle, arbitrary custom definitions are rejected so the declared protocol stays consistent across bundles.
Solutions
- Set definition.PluginService directly from PluginServiceOptions(): &PluginServiceOptions().PluginService
- Remove any hand edits (extra variants, reordered entries, changed statuses) so DeepEqual succeeds
- After a library upgrade, re-copy from PluginServiceOptions() rather than keeping a stored definition
Example fix
// before svc := apicontract.PluginServiceOptions().PluginService svc.Variants = svc.Variants[:3] // keep only JSON modes definition.PluginService = &svc // after definition.PluginService = &apicontract.PluginServiceOptions().PluginService
Defensive patterns
Strategy: validation
Validate before calling
if definition.PluginService != nil &&
!reflect.DeepEqual(*definition.PluginService, apicontract.PluginServiceOptions().PluginService) {
return errors.New("plugin service definition must equal PluginServiceOptions() exactly")
} Try / catch
if err := apicontract.BuildBundle(endpoints); err != nil {
if strings.Contains(err.Error(), "protocol variants") { log.Fatalf("stale plugin service definition: %v", err) }
return err
} Prevention
- Always copy the declaration from PluginServiceOptions(); never trim or reorder variants
- Re-copy the definition after every library upgrade
- Compare against PluginServiceOptions() in a test to catch drift early
When it happens
Trigger: BuildBundle validates a plugin-service endpoint whose PluginService field was hand-constructed or modified (extra/missing variant, changed AdmissionStatuses, altered SSEEvent schema) instead of copied from PluginServiceOptions().
Common situations: Trimming the variants list to 'only the modes I use', reordering variants (DeepEqual is order-sensitive for slices), bumping the canonical options in a library update while a stale cached definition is embedded, or hand-writing the definition from the JSON schema.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- invalid plugin service response options
- plugin service output requires a protocol declaration
- empty plugin response contains a body
- invalid json schema
- invalid plugin redirect status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/34bd7720c254c346.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:128
}
if err := validatePluginServiceStatus(response.pluginServiceMode, response.httpStatus); err != nil {
panic(err)
}
return response.httpStatus
}
func validatePluginServiceDefinition(definition Definition) error {
if (definition.Output == PluginServiceOutput) != (definition.PluginService != nil) {
return fmt.Errorf("plugin service output requires a protocol declaration")
}
if definition.PluginService == nil {
return nil
}
if definition.Data != reflect.TypeFor[PluginServiceContent]() || definition.SSE != nil || definition.Proxy != nil || definition.WebSocket != nil || definition.DataOnError || definition.ErrorStatus != 0 {
return fmt.Errorf("invalid plugin service response options")
}
if !reflect.DeepEqual(definition.PluginService, PluginServiceOptions().PluginService) {
return fmt.Errorf("invalid plugin service protocol variants")
}
return nil
}
func validatePluginServiceStatus(mode PluginServiceMode, status int) error {
if status < 100 || status > 999 {
return fmt.Errorf("invalid plugin service HTTP status: %d", status)
}
known := false
for _, variant := range PluginServiceOptions().PluginService.Variants {
if variant.Mode == mode {
known = true
break
}
}
if !known {
return fmt.Errorf("unknown plugin service mode: %s", mode)
}View on GitHub (pinned to 9f775e8a12)