diff --git a/packages/react-web-cli/src/AblyCliTerminal.test.tsx b/packages/react-web-cli/src/AblyCliTerminal.test.tsx
index 90141195..48bd116c 100644
--- a/packages/react-web-cli/src/AblyCliTerminal.test.tsx
+++ b/packages/react-web-cli/src/AblyCliTerminal.test.tsx
@@ -2467,3 +2467,48 @@ describe("AblyCliTerminal - Initial Command Execution", () => {
expect(hasTestCmd).toBe(true);
}, 15_000);
});
+
+describe("AblyCliTerminal - Unmount cleanup", () => {
+ test("sends close code 4001 (user-closed-panel) on unmount", async () => {
+ mockClose.mockClear();
+
+ const { unmount } = render(
+ ,
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ });
+
+ expect(mockSocketInstance).toBeTruthy();
+ expect(mockSocketInstance.readyState).toBe(WebSocket.OPEN);
+
+ unmount();
+
+ expect(mockClose).toHaveBeenCalledWith(4001, "user-closed-panel");
+ });
+
+ test("does not call close if socket already closing", async () => {
+ mockClose.mockClear();
+
+ const { unmount } = render(
+ ,
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ });
+
+ mockSocketInstance.readyState = WebSocket.CLOSING;
+
+ unmount();
+
+ expect(mockClose).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/react-web-cli/src/AblyCliTerminal.tsx b/packages/react-web-cli/src/AblyCliTerminal.tsx
index bf6c00cf..1ce5781c 100644
--- a/packages/react-web-cli/src/AblyCliTerminal.tsx
+++ b/packages/react-web-cli/src/AblyCliTerminal.tsx
@@ -2419,9 +2419,10 @@ const AblyCliTerminalInner = (
socketReference.current &&
socketReference.current.readyState < WebSocket.CLOSING
) {
- // close websocket
+ // close websocket with code 4001 to signal intentional close
+ // This tells the server to cleanup immediately (no grace period)
debugLog("[AblyCLITerminal] Closing WebSocket on unmount.");
- socketReference.current.close();
+ socketReference.current.close(4001, "user-closed-panel");
}
grResetState(); // Ensure global state is clean
clearConnectionTimeout(); // Clear any pending connection timeout
@@ -3559,7 +3560,7 @@ const AblyCliTerminalInner = (
debugLog(
"[AblyCLITerminal] Closing secondary WebSocket on terminal cleanup.",
);
- secondarySocketReference.current.close();
+ secondarySocketReference.current.close(4001, "user-closed-panel");
secondarySocketReference.current = null;
}