Skip to content

Commit 7357cd5

Browse files
Kenoclaude
andcommitted
Fix bash timeout to properly kill long-running processes
- Use SIGKILL instead of default signal for more forceful termination - Don't wait for killed processes to avoid hanging - Add comprehensive test for 120s sleep with 2s timeout - Timeout now correctly terminates processes within specified time 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 199cc7f commit 7357cd5

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

src/tools/bash.jl

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,26 @@ function execute(tool::BashTool, params::Dict)
7070
while process_running(process)
7171
if time() - start_time > timeout_seconds
7272
timed_out = true
73-
# Kill the process
74-
kill(process)
73+
# Kill the process forcefully
74+
kill(process, Base.SIGKILL)
75+
# Give it a brief moment to terminate
76+
sleep(0.1)
77+
# If still running, force terminate
78+
if process_running(process)
79+
try
80+
# Force kill using SIGKILL
81+
kill(process, Base.SIGKILL)
82+
catch
83+
# Process might already be dead
84+
end
85+
end
7586
break
7687
end
7788
sleep(0.1) # Check every 100ms
7889
end
7990

80-
# Wait for process to fully terminate if we killed it
91+
# Don't wait for process if we killed it - just return timeout error
8192
if timed_out
82-
wait(process)
8393
return create_content_response(
8494
"Error: Command timed out after $timeout_seconds seconds",
8595
is_error=true

test/test_bash_timeout.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,22 @@
5858
@test occursin("timed out", result["content"][1]["text"])
5959
# Note: Output might not be captured due to buffering
6060
end
61+
62+
@testset "Long sleep (120s) with short timeout (2s)" begin
63+
# This test ensures that even very long-running commands get cancelled properly
64+
# The sleep is for 120 seconds but should be killed after 2 seconds
65+
start_time = time()
66+
result = execute(tool, Dict(
67+
"command" => "sleep 120",
68+
"timeout" => 2
69+
))
70+
elapsed_time = time() - start_time
71+
72+
@test result["isError"]
73+
@test occursin("timed out after 2 seconds", result["content"][1]["text"])
74+
# The command should be killed within approximately 2 seconds (plus some overhead)
75+
# We allow up to 5 seconds total to account for process startup/cleanup overhead
76+
@test elapsed_time < 5.0
77+
@test elapsed_time >= 2.0
78+
end
6179
end

0 commit comments

Comments
 (0)