Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ public struct ShellTool: MCPTool {

do {
try process.run()
process.waitUntilExit()

let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
// Either pipe can fill while the other waits for EOF, so drain both concurrently.
async let outputRead = outputPipe.fileHandleForReading.readDataToEndOfFile()
async let errorRead = errorPipe.fileHandleForReading.readDataToEndOfFile()
let (outputData, errorData) = await (outputRead, errorRead)
process.waitUntilExit()

let output = String(data: outputData, encoding: .utf8) ?? ""
let error = String(data: errorData, encoding: .utf8) ?? ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,42 @@ struct MCPSpecificToolTests {
try MCPAgentTool.validatedMaxSteps(101)
}
}

// MARK: - Shell Tool Tests

@Test(.timeLimit(.minutes(1)))
func `Shell tool does not deadlock on large output`() async throws {
let tool = makeTestTool(ShellTool.init)

// Generate output larger than the macOS pipe buffer (~64 KB)
// to trigger a deadlock if waitUntilExit() precedes pipe reads.
let result = try await tool.execute(arguments: ToolArguments(raw: [
"command": "head -c 131072 /dev/zero | base64",
]))

#expect(result.isError == false)
if case let .text(text, _, _) = result.content.first {
#expect(text.count > 100_000)
} else {
Issue.record("Expected text content from shell output")
}
}

@Test(.timeLimit(.minutes(1)))
func `Shell tool does not deadlock when both pipes exceed their buffers`() async throws {
let tool = makeTestTool(ShellTool.init)

let result = try await tool.execute(arguments: ToolArguments(raw: [
"command": "head -c 131072 /dev/zero >&2; head -c 131072 /dev/zero",
]))

#expect(result.isError == false)
if case let .text(text, _, _) = result.content.first {
#expect(text.count == 131_072)
} else {
Issue.record("Expected text content from shell output")
}
}
}

@MainActor
Expand Down
Loading