nats-io/nats-server · error
subject does not match jwt content
Error message
subject does not match jwt content
What it means
consumerMemStore.ForceUpdate performs the same sanity checks as Update but skips the normal state-validity comparison against current state; it still rejects a ConsumerState whose AckFloor.Consumer exceeds Delivered.Consumer. The invariant is fundamental, so even forced updates cannot bypass it.
Source
Thrown at server/accounts.go:4529
for _, reqSub := range []string{accUpdateEventSubjOld, accUpdateEventSubjNew} {
// subscribe to account jwt update requests
if _, err := s.sysSubscribe(fmt.Sprintf(reqSub, "*"), func(_ *subscription, _ *client, _ *Account, subj, resp string, msg []byte) {
var pubKey string
tk := strings.Split(subj, tsep)
if len(tk) == accUpdateTokensNew {
pubKey = tk[accReqAccIndex]
} else if len(tk) == accUpdateTokensOld {
pubKey = tk[accUpdateAccIdxOld]
} else {
s.Debugf("DirResolver - jwt update skipped due to bad subject %q", subj)
return
}
if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil {
respondToUpdate(s, resp, "n/a", "jwt update resulted in error", err)
} else if err := claimValidate(claim); err != nil {
respondToUpdate(s, resp, claim.Subject, "jwt validation failed", err)
} else if claim.Subject != pubKey {
err := errors.New("subject does not match jwt content")
respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err)
} else if claim.Issuer == op && strict {
err := errors.New("operator requires issuer to be a signing key")
respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err)
} else if err := dr.save(pubKey, string(msg)); err != nil {
respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err)
} else {
respondToUpdate(s, resp, pubKey, "jwt updated", nil)
}
}); err != nil {
return fmt.Errorf("error setting up update handling: %v", err)
}
}
if _, err := s.sysSubscribe(accClaimsReqSubj, func(_ *subscription, c *client, _ *Account, _, resp string, msg []byte) {
// As this is a raw message, we need to extract payload and only decode claims from it,
// in case request is sent with headers.
_, msg = c.msgParts(msg)
if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil {View on GitHub (pinned to 3a66a489d2)
Solutions
- Sanitize the state (clamp consumer ack floor to delivered) before ForceUpdate
- Reject the incoming state at the sync layer instead of forcing it
- Re-fetch a consistent consumer state snapshot from the current leader
Example fix
// before
consumer.ForceUpdate(badState) // AckFloor.Consumer > Delivered.Consumer
// after
if badState.AckFloor.Consumer > badState.Delivered.Consumer {
badState.AckFloor.Consumer = badState.Delivered.Consumer
}
consumer.ForceUpdate(badState) Defensive patterns
Strategy: validation
Validate before calling
func validConsumerAckFloor(st *ConsumerState) bool {
return st.AckFloor.Consumer <= st.Delivered.Consumer &&
st.AckFloor.Stream <= st.Delivered.Stream
}
if !validConsumerAckFloor(st) { return ErrInvalidState }
consumer.ForceUpdate(st) Try / catch
if err := consumer.ForceUpdate(st); err != nil {
if strings.Contains(err.Error(), "bad ack floor for consumer") {
st.AckFloor.Consumer = st.Delivered.Consumer
err = consumer.ForceUpdate(st)
}
} Prevention
- Sanitize replicated states at the sync boundary before ForceUpdate
- Share one invariant-check helper for Update and ForceUpdate
- Log the offending state when clamping to aid cluster debugging
When it happens
Trigger: Calling consumerMemStore.ForceUpdate(state) where state.AckFloor.Consumer > state.Delivered.Consumer — typically during consumer state restore or leader takeover in a cluster.
Common situations: JetStream consumer state sync/restore from a peer with corrupt or reordered data; hand-built state in tests or migration tooling.
Related errors
- operator requires issuer to be a signing key
- incomplete type, value pair
- DN ended with incomplete type, value pair
- errors.New(strings.Join(errs, "\n"))
- bad ack floor for stream
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/3cb62f4ec44a0e48.
Report an issue: GitHub.