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 nil

View on GitHub (pinned to 85cf10c09c)

Solutions

  1. Make the client send the ID exactly as received in the message frame: 16 characters, e.g. 'FIN 0f60b4f9d4e368b1' or 'REQ 0f60b4f9d4e368b1 5000'.
  2. Audit the command framing: parameters are whitespace-split, so params[1] must be the ID (REQ also needs a decimal timeout in params[2]).
  3. Use a maintained client library (go-nsq, pynsq, nsqjs) instead of a hand-written protocol implementation.
  4. 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

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.