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
- Delete the corrupted session record (or the whole session) so a fresh one is recreated on next CONNECT
- Verify no external publishers are writing to internal $MQTT.* streams
- Check server version compatibility of stored records (upgrade/downgrade)
- 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
- Never let external publishers write to internal $MQTT.* streams
- Keep all servers on the same version to avoid format mismatches
- Monitor filestore/disk health to catch corruption early
- Recover by clearing the session record and reconnecting with clean session
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
- loading session record: %w
- unable to persist session %q (seq=%v): %v
- unable to delete session %q record at sequence %v: %v
- JS_INVALID_JSON
- MQTT clients over websocket must connect to the Websocket po
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/12e9ebbb2f14d4e2.
Report an issue: GitHub.