{"record":{"id":"eda15740b72cbda7","repo":"nats-io/nats-server","slug":"unsafe-quorum-rescue-applied-quorum-lowered-d","errorCode":null,"errorMessage":"Unsafe quorum rescue applied, quorum lowered %d -> %d for %v","messagePattern":"Unsafe quorum rescue applied, quorum lowered (.+?) -> (.+?) for (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/raft.go","lineNumber":1361,"sourceCode":"\t// If all servers are empty, this requires a cluster bootstrap.\n\tif n.pindex == 0 {\n\t\treturn prev, 0, errRescueEmptyLog\n\t}\n\n\t// Cancel any previous rescue.\n\tif n.rescue != nil {\n\t\tn.rescue.Stop()\n\t}\n\tvar t *time.Timer\n\tt = time.AfterFunc(rescueQuorumTimeout, func() {\n\t\tn.Lock()\n\t\tdefer n.Unlock()\n\t\t// Read of t must be under the lock.\n\t\tn.expireRescueLocked(t)\n\t})\n\tn.rescue = t\n\tn.qn = qn\n\tn.warn(\"Unsafe quorum rescue applied, quorum lowered %d -> %d for %v\", prev, qn, rescueQuorumTimeout)\n\n\t// Make sure an election can happen soon.\n\tn.resetElect(randCampaignTimeout())\n\treturn prev, qn, nil\n}\n\n// expireRescueLocked runs when the rescue timeout fires and restores the natural quorum.\n// Lock should be held.\nfunc (n *raft) expireRescueLocked(t *time.Timer) {\n\tif n.State() == Closed || n.rescue != t {\n\t\treturn\n\t}\n\t// Must clear the timer first, recalcQuorum keeps the rescued quorum\n\t// while it sees an active rescue.\n\tn.rescue = nil\n\tn.recalcQuorum()\n\tn.warn(\"Unsafe quorum rescue expired, quorum restored to %d\", n.qn)\n}","sourceCodeStart":1343,"sourceCodeEnd":1379,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/raft.go#L1343-L1379","documentation":"An operator-driven RescueQuorum lowered the quorum from the natural value to qn for a bounded rescueQuorumTimeout, allowing elections/commits on a degraded cluster. A timer auto-expires the rescue; while active, recalcQuorum preserves the lowered value. This is deliberately unsafe: commits may lack durable majority.","triggerScenarios":"Thrown at server/raft.go:1361 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Restore missing peers before the rescue timeout expires to avoid split-brain risk","Back up RAFT state before using rescue on production clusters","Monitor for the 'rescue expired' log to know when normal quorum returned"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}