nats-io/nats-server · error
stored session does not match client ID
Error message
stored session does not match client ID
What it means
This sentinel error (errMQTTSessionCollision) is returned by the NATS server's MQTT layer when a persisted MQTT session is restored from storage but its recorded client ID does not match the client ID of the connection attempting to resume it. The MQTT session is keyed by the client identifier, so a mismatch means the stored session belongs to a different logical client and cannot be safely reused; the code at server/mqtt.go:3177 compares ps.ID against clientID and fails the restore. Returning it prevents cross-client message/state leakage.
Source
Thrown at server/mqtt.go:247
errMQTTServerNameMustBeSet = errors.New("mqtt requires server name to be explicitly set")
errMQTTUserMixWithUsersNKeys = errors.New("mqtt authentication username not compatible with presence of users/nkeys")
errMQTTTokenMixWIthUsersNKeys = errors.New("mqtt authentication token not compatible with presence of users/nkeys")
errMQTTAckWaitMustBePositive = errors.New("ack wait must be a positive value")
errMQTTJSAPITimeoutMustBePositive = errors.New("JS API timeout must be a positive value")
errMQTTStandaloneNeedsJetStream = errors.New("mqtt requires JetStream to be enabled if running in standalone mode")
errMQTTConnFlagReserved = errors.New("connect flags reserved bit not set to 0")
errMQTTWillAndRetainFlag = errors.New("if Will flag is set to 0, Will Retain flag must be 0 too")
errMQTTPasswordFlagAndNoUser = errors.New("password flag set but username flag is not")
errMQTTCIDEmptyNeedsCleanFlag = errors.New("when client ID is empty, clean session flag must be set to 1")
errMQTTEmptyWillTopic = errors.New("empty Will topic not allowed")
errMQTTEmptyUsername = errors.New("empty user name not allowed")
errMQTTTopicIsEmpty = errors.New("topic cannot be empty")
errMQTTPacketIdentifierIsZero = errors.New("packet identifier cannot be 0")
errMQTTUnsupportedCharacters = errors.New("character not supported for MQTT topics")
errMQTTInvalidSession = errors.New("invalid MQTT session")
errMQTTInvalidRetainFlags = errors.New("invalid retained message flags")
errMQTTInvalidRetainedMessage = errors.New("invalid retained message")
errMQTTSessionCollision = errors.New("stored session does not match client ID")
errMQTTInvalidPublishLength = errors.New("invalid publish message, variable header exceeds remaining length")
errMQTTAckPipelineStopped = errors.New("QoS1 PUBACK pipeline has shut down while admitting a message, " +
"abandoning the wait for its JetStream ack; failing the connection, " +
"the client will re-send unacknowledged PUBLISH packets on reconnect")
)
type srvMQTT struct {
listener net.Listener
listenerErr error
authOverride bool
sessmgr mqttSessionManager
}
type mqttSessionManager struct {
mu sync.RWMutex
sessions map[string]*mqttAccountSessionManager // key is account name
}
View on GitHub (pinned to 3a66a489d2)
Solutions
- Verify each MQTT client uses a unique, stable client ID and that the session store is keyed by that same client ID
- Check that the session persistence store is not shared or contaminated between servers/environments; use per-server or per-cluster namespaced storage
- If stale state is the cause, clear the stored session for that client ID (or have the client reconnect with Clean Session=true to purge it) and reconnect
- Upgrade/patch to a server version where the session store keying matches your deployment topology
Example fix
// before: reusing a shared store with colliding keys
client.Connect(mqtt.NewClientOptions().SetClientID("device-1").SetCleanSession(false))
// after: unique client ID per device + clean start after storage collision
client.Connect(mqtt.NewClientOptions().SetClientID("device-1-" + deviceUUID).SetCleanSession(false)) Defensive patterns
Strategy: validation
Validate before calling
// before connecting with CleanSession=false, ensure the client ID is unique per device
if clientID == "" || !strings.HasPrefix(clientID, deviceUUIDPrefix) {
clientID = deviceUUIDPrefix + deviceUUID
}
// on collision, fall back to a clean session
opts := mqtt.NewClientOptions().SetClientID(clientID).SetCleanSession(firstAttempt == false) Try / catch
// Go: treat the connack failure as a session-store issue
err := nc.Connect()
if err != nil && strings.Contains(err.Error(), "stored session does not match client ID") {
// purge stored session for this client ID, then reconnect with Clean Session
purgeStoredSession(clientID)
reconnect(cleanSession = true)
} Prevention
- Derive client IDs from immutable device identity, never from hostnames or shared defaults
- Do not share MQTT session stores between unrelated servers/clusters
- After restoring snapshots or migrating storage, rebuild or verify session key->client ID mappings
When it happens
Trigger: A client connects with clean session=false and a client ID whose stored session record maps to a different client ID (e.g. the session store was reused across servers, storage keys collided, or a store migration reassigned session keys), causing the ps.ID != clientID check in the session restore path to fail.
Common situations: Sharing one JetStream/stream bucket for MQTT sessions between multiple NATS servers without namespacing; manually editing or migrating persistent session state; resuming sessions after restoring a snapshot where session keys were regenerated; clients reusing a client ID after a server-side session store was rebuilt.
Related errors
- error creating store for stream
- error creating store for consumer
- when client ID is empty, clean session flag must be set to 1
- invalid MQTT session
- invalid publish message, variable header exceeds remaining l
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/ce8425589e2765b6.
Report an issue: GitHub.