siyuan-note/siyuan · error
unknown plugin service mode
Error message
unknown plugin service mode: %s
What it means
validatePluginServiceStatus checks that the response mode passed to a plugin-service endpoint is one of the finite set of registered variants (JSON, JSONP, file, redirect, proxy, websocket, sse, admission, etc.). This error means the mode string did not match any variant, so the kernel refuses to describe or validate a response for it. It surfaces when building/validating the endpoint schema via StreamPluginService, pluginServiceStatus, or ValidatePluginServiceResponse.
Solutions
- Use one of the exported constants (PluginServiceJSON, PluginServiceFile, PluginServiceRedirect, ...) instead of a literal string
- Check the mode value for typos and exact casing (modes are compared with ==, e.g. "JSON" not "json")
- Rebuild the plugin against the current kernel/apicontract version so its mode set matches the registered variants
Example fix
// before
StreamPluginService("json", 200, serve)
// after
StreamPluginService(PluginServiceJSON, 200, serve) Defensive patterns
Strategy: validation
Validate before calling
func isKnownPluginServiceMode(mode apicontract.PluginServiceMode) bool {
for _, v := range apicontract.PluginServiceOptions().PluginService.Variants {
if v.Mode == mode {
return true
}
}
return false
} Try / catch
// StreamPluginService panics on invalid mode; recover at route-registration time:
func safeStream(mode apicontract.PluginServiceMode, status int, serve func(http.ResponseWriter, *http.Request)) (r apicontract.Response[apicontract.PluginServiceContent]) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("plugin service mode rejected: %v", rec)
}
}()
return apicontract.StreamPluginService(mode, status, serve)
} Prevention
- Always use the exported PluginService* constants, never string literals
- Add a unit test that validates every mode your plugin uses via Bundle.ValidatePluginServiceResponse
- Keep the plugin's apicontract dependency version in sync with the kernel
When it happens
Trigger: Calling StreamPluginService (or returning a Response) with a PluginServiceMode value that is not one of the 19 constants in plugin_service_protocol.go (e.g. a custom mode like "html" or an empty string), or invoking Bundle.ValidatePluginServiceResponse with a mode not in the registered variant list.
Common situations: Plugin authors hand-write a mode string instead of using the exported PluginService* constants; a typo or casing mismatch ("json" vs "JSON"); a plugin built against a newer/older apicontract version that added or renamed a variant.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- empty plugin response contains a body
- invalid plugin redirect status
- invalid plugin service HTTP status
- invalid plugin SSE status
- invalid plugin WebSocket status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/47e603ad30b73f8d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:145
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)
}
switch mode {
case PluginServiceAdmission:
if status != 400 && status != 404 && status != 500 && status != 503 {
return fmt.Errorf("undeclared plugin admission status")
}
case PluginServiceRedirect:
if status != 201 && (status < 300 || status > 308) {
return fmt.Errorf("invalid plugin redirect status")
}
case PluginServiceWebSocket:
if status != 101 && status != 400 && status != 500 {
return fmt.Errorf("invalid plugin WebSocket status")
}
case PluginServiceSSE:
if status != 200 && status != 500 {
return fmt.Errorf("invalid plugin SSE status")
}View on GitHub (pinned to 9f775e8a12)