nats-io/nats-server · error
operator requires issuer to be a signing key
Error message
operator requires issuer to be a signing key
What it means
The stream-namespace counterpart of the ForceUpdate consumer-ack-floor check: rejects a state where AckFloor.Stream > Delivered.Stream. ForceUpdate is used in cluster state synchronization, so this error typically surfaces when replicated state is inconsistent.
Source
Thrown at server/accounts.go:4532
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 {
respondToUpdate(s, resp, "n/a", "jwt update resulted in error", err)
} else if claim.Issuer == op && strict {
err := errors.New("operator requires issuer to be a signing key")View on GitHub (pinned to 3a66a489d2)
Solutions
- Clamp AckFloor.Stream to Delivered.Stream before ForceUpdate
- Fix the state replication/replay logic that produced floor > delivered
- Rebuild state from the underlying stream message sequences
Example fix
// before st.AckFloor.Stream, st.Delivered.Stream = 900, 800 consumer.ForceUpdate(st) // after st.AckFloor.Stream = min(st.AckFloor.Stream, st.Delivered.Stream) consumer.ForceUpdate(st)
Defensive patterns
Strategy: validation
Validate before calling
if st.AckFloor.Stream > st.Delivered.Stream {
return fmt.Errorf("replicated state inconsistent: stream ack floor %d > delivered %d",
st.AckFloor.Stream, st.Delivered.Stream)
}
consumer.ForceUpdate(st) Try / catch
if err := consumer.ForceUpdate(st); err != nil {
if strings.Contains(err.Error(), "bad ack floor for stream") {
// request fresh state snapshot from leader instead of forcing
}
} Prevention
- Verify sequence ordering in raft replay before ForceUpdate
- Compare incoming state sequence ranges with local before applying
- Refuse (don't clamp) state from peers in tests to catch bugs early
When it happens
Trigger: Calling consumerMemStore.ForceUpdate(state) where state.AckFloor.Stream > state.Delivered.Stream.
Common situations: Leader election replaying inconsistent raft state; restore-from-backup with mismatched sequence counters; test fixtures with hand-written ConsumerState.
Related errors
- subject does not match jwt content
- 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/5293f5676a7d30e6.
Report an issue: GitHub.