nats-io/nats-server · error

unmarshal of session record at sequence %v: %w

Error message

unmarshal of session record at sequence %v: %w

What it means

Wraps a json.Unmarshal failure when decoding a persisted mqttPersistedSession record read from JetStream. If the stored bytes cannot decode into mqttPersistedSession, the session cannot be recovered, so the server fails the CONNECT rather than silently corrupting state.

Source

Thrown at server/mqtt.go:3174

func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opts *Options) (*mqttSession, bool, error) {
	jsa := &as.jsa

	hash := getHash(clientID)
	smsg, err := jsa.loadSessionMsg(as.domainTk, hash)
	if err != nil {
		if isErrorOtherThan(err, JSNoMessageFoundErr) {
			return nil, false, fmt.Errorf("loading session record: %w", err)
		}
		// Message not found, so reate the session...
		// Create a session and indicate that this session did not exist.
		sess := mqttSessionCreate(jsa, clientID, hash, 0, opts)
		sess.domainTk = as.domainTk
		return sess, false, nil
	}
	// We need to recover the existing record now.
	ps := &mqttPersistedSession{}
	if err := json.Unmarshal(smsg.Data, ps); err != nil {
		return nil, false, fmt.Errorf("unmarshal of session record at sequence %v: %w", smsg.Sequence, err)
	}
	if ps.ID != clientID {
		return nil, false, errMQTTSessionCollision
	}
	for sid, cc := range ps.Cons {
		if cc == nil {
			delete(ps.Cons, sid)
		}
	}

	// Restore this session (even if we don't own it), the caller will do the right thing.
	sess := mqttSessionCreate(jsa, clientID, hash, smsg.Sequence, opts)
	sess.domainTk = as.domainTk
	sess.clean = ps.Clean
	sess.subs = ps.Subs
	sess.cons = ps.Cons
	sess.pubRelConsumer = ps.PubRel
	as.addSession(sess, true)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Delete the corrupted session record (or the whole session) so a fresh one is recreated on next CONNECT
  2. Verify no external publishers are writing to internal $MQTT.* streams
  3. Check server version compatibility of stored records (upgrade/downgrade)
  4. Inspect underlying disk/filestore health for corruption

Example fix

// before
// corrupted record at seq N blocks reconnect
// after
nats stream msg delete $MQTT.sess <seq>  // session recreated on next CONNECT
null
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await mqttConnect()
} catch (e) {
  if (String(e).includes('unmarshal of session record')) {
    // discard the corrupted session by reconnecting with clean_session=true
    await mqttConnect({ cleanSession: true })
  } else { throw e }
}

Prevention

When it happens

Trigger: The message stored in the '$MQTT.sess' stream at smsg.Sequence is not valid JSON for mqttPersistedSession — corrupted record, truncated write, wrong payload, or a record written by a different/incompatible format.

Common situations: Disk corruption or partial write on the JetStream filestore; manual edits/republishing into the internal $MQTT streams; version upgrade/downgrade with changed persistence format; garbage injected into internal streams.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/12e9ebbb2f14d4e2. Report an issue: GitHub.