siyuan-note/siyuan · error
invalid plugin WebSocket status
Error message
invalid plugin WebSocket status
What it means
WebSocket-mode plugin service responses may only use status 101 (switching protocols, successful handshake), 400, or 500; validatePluginServiceStatus rejects other statuses. This keeps the contract's websocket variant aligned with the HTTP upgrade handshake semantics.
Solutions
- Use 101 for a successful websocket upgrade, or 400/500 for failures
- Replace non-handshake rejections (401/403/426) with the contract-permitted 400
- Serve non-upgrade logic through a different endpoint/variant instead of the websocket variant
Example fix
// before StreamPluginService(PluginServiceWebSocket, http.StatusUpgradeRequired, serve) // after StreamPluginService(PluginServiceWebSocket, 400, serve)
Defensive patterns
Strategy: validation
Validate before calling
func validWebSocketStatus(status int) bool {
return status == 101 || status == 400 || status == 500
} Try / catch
defer func() {
if rec := recover(); rec != nil {
log.Printf("invalid websocket status: %v", rec)
}
}() // around StreamPluginService(PluginServiceWebSocket, ...) Prevention
- Only 101 for upgrades, 400/500 for handshake failures
- Do not apply HTTP auth codes (401/403) to websocket handshake responses; perform auth before upgrading
- Test both the success (101) and failure (400/500) handshake paths
When it happens
Trigger: Calling StreamPluginService(PluginServiceWebSocket, status, serve) or Bundle.ValidatePluginServiceResponse with websocket mode and a status other than 101, 400, or 500 (e.g. 200, 403, or 426).
Common situations: Trying to answer a websocket handshake with a plain 200 OK; rejecting an upgrade with 401/403 (use 400 instead per the contract); emitting 426 Upgrade Required instead of the contract's allowed set.
Related errors
- invalid plugin redirect status
- invalid plugin SSE status
- undeclared plugin admission status
- empty plugin response contains a body
- invalid plugin service HTTP status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/6e8e728759e013ba.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:158
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")
}
}
return nil
}
func (b *Bundle) validatePluginServiceHTTPResponse(endpoint EndpointSchema, status int, contentType string, payload []byte) error {
if status < 100 || status > 999 {
return fmt.Errorf("invalid plugin service HTTP status")
}
if endpoint.Method == "HEAD" || status < 200 || status == 204 || status == 304 {
if len(payload) > 0 {
return fmt.Errorf("plugin service response forbids a body")
}
return nilView on GitHub (pinned to 9f775e8a12)