diff --git a/src/NATS.Server/ClientClosedReason.cs b/src/NATS.Server/ClientClosedReason.cs
new file mode 100644
index 0000000..01eec58
--- /dev/null
+++ b/src/NATS.Server/ClientClosedReason.cs
@@ -0,0 +1,51 @@
+namespace NATS.Server;
+
+///
+/// Reason a client connection was closed.
+/// Corresponds to Go server/client.go ClosedState (subset for single-server scope).
+///
+public enum ClientClosedReason
+{
+ None = 0,
+ ClientClosed,
+ AuthenticationTimeout,
+ AuthenticationViolation,
+ TlsHandshakeError,
+ SlowConsumerPendingBytes,
+ SlowConsumerWriteDeadline,
+ WriteError,
+ ReadError,
+ ParseError,
+ StaleConnection,
+ ProtocolViolation,
+ MaxPayloadExceeded,
+ MaxSubscriptionsExceeded,
+ ServerShutdown,
+ MsgHeaderViolation,
+ NoRespondersRequiresHeaders,
+}
+
+public static class ClientClosedReasonExtensions
+{
+ public static string ToReasonString(this ClientClosedReason reason) => reason switch
+ {
+ ClientClosedReason.None => "",
+ ClientClosedReason.ClientClosed => "Client Closed",
+ ClientClosedReason.AuthenticationTimeout => "Authentication Timeout",
+ ClientClosedReason.AuthenticationViolation => "Authorization Violation",
+ ClientClosedReason.TlsHandshakeError => "TLS Handshake Error",
+ ClientClosedReason.SlowConsumerPendingBytes => "Slow Consumer (Pending Bytes)",
+ ClientClosedReason.SlowConsumerWriteDeadline => "Slow Consumer (Write Deadline)",
+ ClientClosedReason.WriteError => "Write Error",
+ ClientClosedReason.ReadError => "Read Error",
+ ClientClosedReason.ParseError => "Parse Error",
+ ClientClosedReason.StaleConnection => "Stale Connection",
+ ClientClosedReason.ProtocolViolation => "Protocol Violation",
+ ClientClosedReason.MaxPayloadExceeded => "Maximum Payload Exceeded",
+ ClientClosedReason.MaxSubscriptionsExceeded => "Maximum Subscriptions Exceeded",
+ ClientClosedReason.ServerShutdown => "Server Shutdown",
+ ClientClosedReason.MsgHeaderViolation => "Message Header Violation",
+ ClientClosedReason.NoRespondersRequiresHeaders => "No Responders Requires Headers",
+ _ => reason.ToString(),
+ };
+}
diff --git a/tests/NATS.Server.Tests/ClientClosedReasonTests.cs b/tests/NATS.Server.Tests/ClientClosedReasonTests.cs
new file mode 100644
index 0000000..16edf19
--- /dev/null
+++ b/tests/NATS.Server.Tests/ClientClosedReasonTests.cs
@@ -0,0 +1,24 @@
+namespace NATS.Server.Tests;
+
+public class ClientClosedReasonTests
+{
+ [Fact]
+ public void All_expected_close_reasons_exist()
+ {
+ // Verify all 17 enum values exist and are distinct (None + 16 named reasons)
+ var values = Enum.GetValues();
+ values.Length.ShouldBe(17);
+ values.Distinct().Count().ShouldBe(17);
+ }
+
+ [Theory]
+ [InlineData(ClientClosedReason.ClientClosed, "Client Closed")]
+ [InlineData(ClientClosedReason.SlowConsumerPendingBytes, "Slow Consumer (Pending Bytes)")]
+ [InlineData(ClientClosedReason.SlowConsumerWriteDeadline, "Slow Consumer (Write Deadline)")]
+ [InlineData(ClientClosedReason.StaleConnection, "Stale Connection")]
+ [InlineData(ClientClosedReason.ServerShutdown, "Server Shutdown")]
+ public void ToReasonString_returns_human_readable_description(ClientClosedReason reason, string expected)
+ {
+ reason.ToReasonString().ShouldBe(expected);
+ }
+}