Error Frames
The complete ERROR-frame vocabulary the KubeMQ STOMP connector emits — handshake, codec, dispatch, SEND, SUBSCRIBE, and RPC errors, plus receipt rules.
This is the complete, verified vocabulary of ERROR-frame message strings the KubeMQ STOMP
connector emits — the trigger for each, the receipt rules, and the one failure mode that is not
an ERROR frame at all: the RPC stomp-error header.
ERROR is terminal
Every protocol error produces an ERROR frame followed immediately by a socket close. There
is no recovery on the same connection — the client must reconnect. The message string is
sanitized: internal error text, stack chains, and file paths never cross the wire.
The connector has three ERROR-emit paths, all of which close the connection:
| Function | Phase |
|---|---|
writeErrorSync | handshake (before the write loop starts) |
sendError | post-CONNECT frame dispatch |
sendErrorAsync | connector shutdown broadcast |
An ERROR frame carries message:<sanitized text> and, when a detail is provided, a
content-type:text/plain body with a short human-readable explanation.
The ERROR-frame vocabulary (complete)
Every message string the connector can emit, grouped by phase. The detail column shows the
optional ERROR body where one is set.
Handshake errors (before CONNECTED)
message | Detail body | Trigger |
|---|---|---|
malformed frame | first frame must be CONNECT or STOMP | first frame is not CONNECT / STOMP within 30 s |
broker not ready | — | the message broker not ready at CONNECT (gate before auth) |
connection limit reached | — | MaxConnections exceeded (counted at accept; ERROR deferred to handshake) |
authentication failed | — | passcode JWT rejected (no detail — no leakage) |
| version-negotiation ERROR | — | no common accept-version; the ERROR carries version:1.0,1.1,1.2 |
Frame-codec errors (read loop)
message | Detail body | Trigger |
|---|---|---|
frame too large | — | frame exceeds 64 headers / 8 KiB header block / MaxBodySize |
malformed frame | — | unparseable frame; body on a non-SEND command; bad escaping; lone CR |
Dispatch errors (post-CONNECT)
message | Detail body | Trigger |
|---|---|---|
transactions not supported | — | BEGIN / COMMIT / ABORT, or any frame carrying a transaction header |
malformed frame | already connected | a second CONNECT / STOMP after the handshake |
unknown command | — (offending command echoed, sanitized) | an unrecognized command |
SEND errors
message | Detail body | Trigger |
|---|---|---|
invalid destination | missing destination header | SEND with no / empty destination |
invalid destination | — | bad destination grammar (empty segment, empty channel, no pattern, too long, wildcard on SEND) |
invalid destination | cannot SEND to reply destinations | SEND to a /reply/ destination |
access denied | — | Casbin Enforce(write) denied |
broker not ready | — | the message broker not ready at SEND time (SENDs do not buffer) |
frame too large | too many headers or header value too large | >32 custom tags or a tag value >4096 bytes |
message rejected | — | the array publish call returned an error (sanitized; detail in server log) |
SUBSCRIBE errors
message | Detail body | Trigger |
|---|---|---|
selectors not supported | — | a selector header on SUBSCRIBE |
invalid destination | missing destination header | SUBSCRIBE with no / empty destination |
invalid subscription | id header required | no id on 1.1 / 1.2 |
invalid subscription | unknown ack mode | ack is not auto / client / client-individual |
invalid destination | — | bad destination grammar |
cannot subscribe to RPC destinations | — | SUBSCRIBE to /command/ or /query/ (STOMP is requester-only) |
invalid subscription | duplicate subscription id | the id is already in use on this connection |
access denied | — | Casbin Enforce(read) denied (queues / events / store) |
invalid subscription | <replay error> | bad Events-Store start-from / start-value combination |
UNSUBSCRIBE / ACK / NACK errors
message | Detail body | Trigger |
|---|---|---|
invalid subscription | missing id | UNSUBSCRIBE with no resolvable id |
invalid subscription | unknown subscription id | UNSUBSCRIBE for an id this connection never created |
ACK / NACK never close on an unknown, expired, or foreign token — the late ack is silently ignored and the RECEIPT (if requested) is still sent.
RPC SEND errors (the only RPC failures that close the connection)
message | Detail body | Trigger |
|---|---|---|
reply-to subscription required | — | the reply-to does not name an active /reply/ sub on the same connection |
too many pending requests | — | RpcMaxPending (default 1024) exceeded |
Everything else about an RPC failure (timeout, logical error, dropped reply) is not an
ERROR frame — see RPC stomp-error.
Shutdown
message | Detail body | Trigger |
|---|---|---|
server shutting down | — | connector Close() broadcasts to every live connection, then drains ≤10 s |
Receipt semantics
The receipt header requests a RECEIPT confirmation frame. The rules:
- A
receiptheader is honored on every processed client frame (SEND, SUBSCRIBE, UNSUBSCRIBE, ACK, NACK, DISCONNECT). RECEIPTis enqueued after the frame's processing completes:- SEND (events / store / queues): after the array call returns successfully.
- RPC SEND (
/command//query): after dispatch acceptance — before the reply MESSAGE arrives.
- A RECEIPT means "KubeMQ accepted the frame", NOT "a consumer received the message." Do not treat a SEND RECEIPT as a delivery confirmation.
Handlers that close the connection on an ERROR path NEVER send a RECEIPT. The STOMP spec
permits an ERROR in lieu of a RECEIPT. So a SEND to a bad destination that carried a receipt
header returns an ERROR and no RECEIPT — never block forever waiting for one.
A DISCONNECT with a receipt header flushes the RECEIPT to the wire before the socket
closes (deterministic graceful shutdown). This is the clean way to confirm a graceful close.
RPC stomp-error: a failure that is NOT an ERROR frame
RPC failures arrive as a MESSAGE with a stomp-error header, NOT an ERROR frame. A timeout, a
logical error, or a dropped reply is delivered as data on your /reply/ subscription, and the
connection stays open. Only reply-to violations and pending-cap overflow (above) close the
connection.
The reply MESSAGE has three shapes, only two of which are empty-bodied:
| Shape | Condition | stomp-error value | Body |
|---|---|---|---|
| (a) Logical error | resp.Error != "" && !resp.Executed | <sanitized resp.Error> | carries the responder's body + tags — NOT empty |
| (b) Transport error / timeout | err != nil | timeout / request cancelled / <sanitized err> | empty |
| (c) Nil response | resp == nil | no response | empty |
Detect RPC failure by the presence of the stomp-error header, NOT by an empty body. A
logical error (shape a) returns a fully-populated body and tags alongside stomp-error. Code that
keys on "empty body" will misclassify logical errors.
Go context errors map to stable strings:
| Go error contains | stomp-error value |
|---|---|
context deadline exceeded | timeout |
context canceled | request cancelled |
| (anything else) | the sanitized error text |
A reply MESSAGE is best-effort — if the connection's output queue is full it is dropped (metric
rpc_response / dropped), again without closing the connection. See
Commands and
Queries.
Error-detail sanitization
Every destination-class trigger is mapped to one of three sanitized strings before it reaches the wire:
| Trigger class | Wire message |
|---|---|
| frame-too-large violations | frame too large |
| empty segment / empty channel / no pattern / too long / wildcard not allowed | invalid destination |
| anything else | malformed frame |
Header values and command names that are echoed in an ERROR (for example, the offending command in
unknown command) are sanitized first.
Related
Was this page helpful?