{"record":{"id":"a865ca5899105f2d","repo":"nats-io/nats-server","slug":"js-stream-purge-failed","errorCode":"JS_STREAM_PURGE_FAILED","errorMessage":"stream purge not permitted","messagePattern":"stream purge not permitted","errorType":"error_code","errorClass":"JSStreamPurgeFailedError","httpStatus":null,"severity":"error","filePath":"server/jetstream_api.go","lineNumber":4034,"sourceCode":"\t\tpurgeRequest = &req\n\t}\n\n\tmset, err := acc.lookupStream(stream)\n\tif err != nil {\n\t\tresp.Error = NewJSStreamNotFoundError(Unless(err))\n\t\ts.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))\n\t\treturn\n\t}\n\tmset.cfgMu.RLock()\n\tsealed, denyPurge := mset.cfg.Sealed, mset.cfg.DenyPurge\n\tmset.cfgMu.RUnlock()\n\tif sealed {\n\t\tresp.Error = NewJSStreamSealedError()\n\t\ts.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))\n\t\treturn\n\t}\n\tif denyPurge {\n\t\tresp.Error = NewJSStreamPurgeFailedError(errors.New(\"stream purge not permitted\"))\n\t\ts.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))\n\t\treturn\n\t}\n\n\tif s.JetStreamIsClustered() {\n\t\ts.jsClusteredStreamPurgeRequest(ci, acc, mset, stream, subject, reply, rmsg, purgeRequest)\n\t\treturn\n\t}\n\n\tpurged, err := mset.purge(purgeRequest)\n\tif err != nil {\n\t\tresp.Error = NewJSStreamGeneralError(err, Unless(err))\n\t} else {\n\t\tresp.Purged = purged\n\t\tresp.Success = true\n\t}\n\ts.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp))\n}","sourceCodeStart":4016,"sourceCodeEnd":4052,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_api.go#L4016-L4052","documentation":"A JetStream API error (JS_STREAM_PURGE_FAILED) returned by the stream purge request handler when the stream's configuration denies purging (DenyPurge). The purge request is rejected upfront with the explicit 'stream purge not permitted' error rather than attempting the operation.","triggerScenarios":"Sending $JS.API.STREAM.PURGE.<stream> for a stream whose StreamConfig has DenyPurge: true; the denyPurge flag is derived from config and short-circuits before plain or clustered purge execution (same pattern as sealed-stream rejection just above it).","commonSituations":"Purging an audit/compliance stream created with DenyPurge; environments where streams were recreated with stricter retention flags than the tooling expects; scripts cleaning test streams hitting production-like configs.","solutions":["Update/recreate the stream with DenyPurge: false if purging is intended.","Use max-age/max-msgs/limits-based retention to expire messages instead of explicit purge.","Check StreamInfo().Config.DenyPurge before issuing purge calls and skip gracefully.","If data must be removed from a DenyPurge stream, delete and recreate the stream (if policy allows)."],"exampleFix":"// before\n_, err := js.PurgeStream(\"AUDIT\") // DenyPurge stream\n// after\nsi, _ := js.StreamInfo(\"AUDIT\")\nif si.Config.DenyPurge {\n    return fmt.Errorf(\"stream AUDIT denies purge\")\n}\n_, err := js.PurgeStream(\"AUDIT\")","handlingStrategy":"validation","validationCode":"si, err := js.StreamInfo(name)\nif err != nil { return err }\nif si.Config.DenyPurge { return fmt.Errorf(\"stream %s denies purge\", name) }\n","typeGuard":"func canPurge(si *nats.StreamInfo) bool { return si != nil && !si.Config.DenyPurge }","tryCatchPattern":"_, err := js.PurgeStream(name)\nvar apiErr *nats.APIError\nif errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamPurgeFailed {\n    return fmt.Errorf(\"purge not permitted on %s: %w\", name, err)\n}","preventionTips":["Check StreamInfo().Config.DenyPurge before purge operations","Use retention limits (max_age/max_msgs) for automatic cleanup","Keep stream config templates consistent across environments","Treat purge denial as a permanent policy error, not retryable"],"tags":["jetstream","permissions","stream-config","purge"],"backgroundTag":"operation-not-permitted","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}