nsqio/nsq · error · FatalClientErr
E_INVALID
E_INVALID
Error message
invalid message ID
What it means
getMessageID (nsqd/protocol_v2.go:1051) validates that the message ID parameter of the FIN, REQ and TOUCH commands is exactly MsgIDLength (16) bytes, then reinterprets it in place as a *MessageID. If the parameter is any other length it returns 'invalid message ID', which the command handlers wrap as a fatal E_INVALID client error — nsqd closes the connection because the client violated the protocol. Message IDs on the wire are the 16-character hex IDs returned in the message frame (e.g. from MPUB/DPUB responses and delivered messages).
Source
Thrown at nsqd/protocol_v2.go:1053
fmt.Sprintf("MPUB message too big %d > %d", messageSize, maxMessageSize))
}
msgBody := make([]byte, messageSize)
_, err = io.ReadFull(r, msgBody)
if err != nil {
return nil, protocol.NewFatalClientErr(err, "E_BAD_MESSAGE", "MPUB failed to read message body")
}
messages = append(messages, NewMessage(topic.GenerateID(), msgBody))
}
return messages, nil
}
// validate and cast the bytes on the wire to a message ID
func getMessageID(p []byte) (*MessageID, error) {
if len(p) != MsgIDLength {
return nil, errors.New("invalid message ID")
}
return (*MessageID)(unsafe.Pointer(&p[0])), nil
}
func readLen(r io.Reader, tmp []byte) (int32, error) {
_, err := io.ReadFull(r, tmp)
if err != nil {
return 0, err
}
return int32(binary.BigEndian.Uint32(tmp)), nil
}
func enforceTLSPolicy(client *clientV2, p *protocolV2, command []byte) error {
if p.nsqd.getOpts().TLSRequired != TLSNotRequired && atomic.LoadInt32(&client.TLS) != 1 {
return protocol.NewFatalClientErr(nil, "E_INVALID",
fmt.Sprintf("cannot %s in current state (TLS required)", command))
}
return nilView on GitHub (pinned to 85cf10c09c)
Solutions
- Make the client send the ID exactly as received in the message frame: 16 characters, e.g. 'FIN 0f60b4f9d4e368b1' or 'REQ 0f60b4f9d4e368b1 5000'.
- Audit the command framing: parameters are whitespace-split, so params[1] must be the ID (REQ also needs a decimal timeout in params[2]).
- Use a maintained client library (go-nsq, pynsq, nsqjs) instead of a hand-written protocol implementation.
- If you intentionally send non-standard commands in tests, expect the connection to be dropped — this error is fatal by design.
Example fix
# before (wrong ID format -> E_INVALID invalid message ID, connection closed) FIN 1663222768521003000 # after (16-char hex message ID as delivered in the frame) FIN 0f60b4f9d4e368b1
Defensive patterns
Strategy: validation
Validate before calling
// client-side: validate a FIN/REQ/TOUCH parameter before writing the command
func validMessageID(id string) bool {
if len(id) != 16 { // nsqd Message.MsgIDLength
return false
}
_, err := hex.DecodeString(id)
return err == nil
}
if !validMessageID(idParam) {
return fmt.Errorf("refusing to send invalid message ID %q", idParam)
} Type guard
func validMessageID(id string) bool {
if len(id) != 16 {
return false
}
_, err := hex.DecodeString(id)
return err == nil
} Try / catch
// only relevant if you implement the protocol by hand: on E_INVALID fatal errors
// the socket is closed by nsqd; catch the read error, reconnect, and resubscribe.
if err := conn.readLoop(); err != nil {
if strings.Contains(err.Error(), "E_INVALID") {
log.Printf("protocol bug: invalid command sent; reconnecting")
reconnect()
}
} Prevention
- Always echo back the ID bytes exactly as delivered in the message frame.
- Prefer maintained client libraries (go-nsq, pynsq) over hand-rolled protocol code.
- In custom clients, unit-test command serialization against the 16-byte ID rule.
When it happens
Trigger: A client sends FIN <id>, REQ <id> <timeout-ms> or TOUCH <id> where <id> is not exactly 16 bytes: truncated/copy-pasted IDs, decimal counters, raw binary IDs, or a malformed hand-rolled protocol implementation that splits the line incorrectly so params[1] is not the ID. Standard clients like go-nsq always send the 16-char hex ID and never trip this.
Common situations: Custom protocol clients (telnet scripting, third-party ports) that guess the ID format; a client upgraded from an old fork that used different ID lengths; a proxy mangling whitespace and shifting parameters; sending the wrong field (timestamp or attempt count) instead of the ID.
Related errors
AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16).
Data as JSON: /api/errors/4f0c386ddf54ad95.
Report an issue: GitHub.