{"record":{"id":"4f0c386ddf54ad95","repo":"nsqio/nsq","slug":"e-invalid","errorCode":"E_INVALID","errorMessage":"invalid message ID","messagePattern":"invalid message ID","errorType":"validation","errorClass":"FatalClientErr","httpStatus":null,"severity":"error","filePath":"nsqd/protocol_v2.go","lineNumber":1053,"sourceCode":"\t\t\t\tfmt.Sprintf(\"MPUB message too big %d > %d\", messageSize, maxMessageSize))\n\t\t}\n\n\t\tmsgBody := make([]byte, messageSize)\n\t\t_, err = io.ReadFull(r, msgBody)\n\t\tif err != nil {\n\t\t\treturn nil, protocol.NewFatalClientErr(err, \"E_BAD_MESSAGE\", \"MPUB failed to read message body\")\n\t\t}\n\n\t\tmessages = append(messages, NewMessage(topic.GenerateID(), msgBody))\n\t}\n\n\treturn messages, nil\n}\n\n// validate and cast the bytes on the wire to a message ID\nfunc getMessageID(p []byte) (*MessageID, error) {\n\tif len(p) != MsgIDLength {\n\t\treturn nil, errors.New(\"invalid message ID\")\n\t}\n\treturn (*MessageID)(unsafe.Pointer(&p[0])), nil\n}\n\nfunc readLen(r io.Reader, tmp []byte) (int32, error) {\n\t_, err := io.ReadFull(r, tmp)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn int32(binary.BigEndian.Uint32(tmp)), nil\n}\n\nfunc enforceTLSPolicy(client *clientV2, p *protocolV2, command []byte) error {\n\tif p.nsqd.getOpts().TLSRequired != TLSNotRequired && atomic.LoadInt32(&client.TLS) != 1 {\n\t\treturn protocol.NewFatalClientErr(nil, \"E_INVALID\",\n\t\t\tfmt.Sprintf(\"cannot %s in current state (TLS required)\", command))\n\t}\n\treturn nil","sourceCodeStart":1035,"sourceCodeEnd":1071,"githubUrl":"https://github.com/nsqio/nsq/blob/85cf10c09c6c3c86160d6f0eb156f62d0efc1648/nsqd/protocol_v2.go#L1035-L1071","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (wrong ID format -> E_INVALID invalid message ID, connection closed)\nFIN 1663222768521003000\n\n# after (16-char hex message ID as delivered in the frame)\nFIN 0f60b4f9d4e368b1","handlingStrategy":"validation","validationCode":"// client-side: validate a FIN/REQ/TOUCH parameter before writing the command\nfunc validMessageID(id string) bool {\n    if len(id) != 16 { // nsqd Message.MsgIDLength\n        return false\n    }\n    _, err := hex.DecodeString(id)\n    return err == nil\n}\n\nif !validMessageID(idParam) {\n    return fmt.Errorf(\"refusing to send invalid message ID %q\", idParam)\n}","typeGuard":"func validMessageID(id string) bool {\n    if len(id) != 16 {\n        return false\n    }\n    _, err := hex.DecodeString(id)\n    return err == nil\n}","tryCatchPattern":"// only relevant if you implement the protocol by hand: on E_INVALID fatal errors\n// the socket is closed by nsqd; catch the read error, reconnect, and resubscribe.\nif err := conn.readLoop(); err != nil {\n    if strings.Contains(err.Error(), \"E_INVALID\") {\n        log.Printf(\"protocol bug: invalid command sent; reconnecting\")\n        reconnect()\n    }\n}","preventionTips":["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."],"tags":["protocol","validation","nsqd","client-error","fatal","message-id"],"backgroundTag":null,"analyzedSha":"85cf10c09c6c3c86160d6f0eb156f62d0efc1648","analyzedAt":"2026-08-16T00:53:05.009Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}