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

  1. Use one of the exported constants (PluginServiceJSON, PluginServiceFile, PluginServiceRedirect, ...) instead of a literal string
  2. Check the mode value for typos and exact casing (modes are compared with ==, e.g. "JSON" not "json")
  3. 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

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


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)