From dc4bac3a85d39f18dea9ffeda2b3e52624a7727c Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 31 Mar 2026 14:48:47 +0000 Subject: [PATCH] fix: update /help test to expect botCommand dispatch, fix PTY fd leak in claude_code.rs (#451, #452) The /help test expected the help overlay to appear, but /help now goes through botCommand like other slash commands. Updated the test to match. Also added reader thread join and child.wait() calls to claude_code.rs to prevent PTY master fd leaks from web UI chat sessions. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/components/Chat.test.tsx | 12 +++++++++--- server/src/llm/providers/claude_code.rs | 7 ++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Chat.test.tsx b/frontend/src/components/Chat.test.tsx index a43ed49a..665be372 100644 --- a/frontend/src/components/Chat.test.tsx +++ b/frontend/src/components/Chat.test.tsx @@ -1642,7 +1642,8 @@ describe("Slash command handling (Story 374)", () => { expect(mockedApi.botCommand).not.toHaveBeenCalled(); }); - it("AC: /help shows help overlay", async () => { + it("AC: /help calls botCommand and displays response", async () => { + mockedApi.botCommand.mockResolvedValue({ response: "Available commands: status, help, ..." }); render(); await waitFor(() => expect(capturedWsHandlers).not.toBeNull()); @@ -1658,9 +1659,14 @@ describe("Slash command handling (Story 374)", () => { fireEvent.keyDown(input, { key: "Enter", shiftKey: false }); }); - expect(await screen.findByTestId("help-overlay")).toBeInTheDocument(); + await waitFor(() => { + expect(mockedApi.botCommand).toHaveBeenCalledWith( + "help", + "", + undefined, + ); + }); expect(lastSendChatArgs).toBeNull(); - expect(mockedApi.botCommand).not.toHaveBeenCalled(); }); it("AC: botCommand API error shows error message in chat", async () => { diff --git a/server/src/llm/providers/claude_code.rs b/server/src/llm/providers/claude_code.rs index ae2a82fe..6bcdb277 100644 --- a/server/src/llm/providers/claude_code.rs +++ b/server/src/llm/providers/claude_code.rs @@ -256,7 +256,7 @@ fn run_pty_session( // Read NDJSON lines from stdout let (line_tx, line_rx) = std::sync::mpsc::channel::>(); - std::thread::spawn(move || { + let reader_handle = std::thread::spawn(move || { let buf_reader = BufReader::new(reader); slog!("[pty-debug] Reader thread started"); for line in buf_reader.lines() { @@ -284,6 +284,8 @@ fn run_pty_session( loop { if cancelled.load(Ordering::Relaxed) { let _ = child.kill(); + let _ = child.wait(); + let _ = reader_handle.join(); return Err("Cancelled".to_string()); } @@ -365,8 +367,11 @@ fn run_pty_session( } // If still running after 2s, kill it let _ = child.kill(); + let _ = child.wait(); } } + // Wait for the reader thread to release the cloned PTY master fd. + let _ = reader_handle.join(); Ok(()) }