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

  1. Clamp AckFloor.Stream to Delivered.Stream before ForceUpdate
  2. Fix the state replication/replay logic that produced floor > delivered
  3. 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

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


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