Skip to content

Commit 4349392

Browse files
committed
Guard non-positive mcp.timeoutMs to the default
1 parent 6814a74 commit 4349392

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

src/tui/tool-execution-watchdog.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,19 @@ describe("tool execution watchdog", () => {
148148
).toBe(100);
149149
});
150150

151+
test("non-positive or non-finite mcp.timeoutMs falls back to the default instead of a 1ms timeout", () => {
152+
const call = { id: "1", name: "mcp__linear__get_issue", arguments: {} };
153+
expect(resolveToolExecutionTimeoutMs({ mcpTimeoutMs: 0 }, call)).toBe(
154+
DEFAULT_MCP_TOOL_TIMEOUT_MS,
155+
);
156+
expect(resolveToolExecutionTimeoutMs({ mcpTimeoutMs: -5 }, call)).toBe(
157+
DEFAULT_MCP_TOOL_TIMEOUT_MS,
158+
);
159+
expect(resolveToolExecutionTimeoutMs({ mcpTimeoutMs: NaN }, call)).toBe(
160+
DEFAULT_MCP_TOOL_TIMEOUT_MS,
161+
);
162+
});
163+
151164
test("withTimeout dispose clears timer without leaving hung state", async () => {
152165
const parent = new AbortController();
153166
const budget = withTimeout(parent.signal, 50);
@@ -233,6 +246,23 @@ describe("tool execution watchdog", () => {
233246
10_000,
234247
);
235248

249+
test(
250+
"mcp.timeoutMs: 0 does not instantly time out an mcp tool call (falls back to the default)",
251+
async () => {
252+
const runner = createDynamicToolRunner(
253+
[stringTool("mcp__linear__get_issue", async () => "ok")],
254+
{ mcpTimeoutMs: 0 },
255+
);
256+
const result = await runner.run(
257+
{ id: "1", name: "mcp__linear__get_issue", arguments: {} },
258+
new AbortController().signal,
259+
);
260+
expect(result.isError).toBeUndefined();
261+
expect(result.content).toBe("ok");
262+
},
263+
10_000,
264+
);
265+
236266
test("parent cancel prefers execute salvage body over synthetic aborted", async () => {
237267
const parent = new AbortController();
238268
const salvage = {

src/tui/tool-execution-watchdog.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,14 @@ export function resolveToolExecutionTimeoutMs(
9797

9898
function resolveMcpToolTimeoutMs(config: ToolWatchdogConfig | undefined): number {
9999
const max = config?.maxMs ?? MAX_TOOL_EXECUTION_TIMEOUT_MS;
100-
const raw = config?.mcpTimeoutMs ?? DEFAULT_MCP_TOOL_TIMEOUT_MS;
100+
const configured = config?.mcpTimeoutMs;
101+
// A non-positive or non-finite configured value is not a valid budget (it
102+
// would floor to a ~0ms timeout and instantly fail every MCP call, which is
103+
// unconditionally armed) — fall back to the default instead of clamping to 1ms.
104+
const raw =
105+
configured !== undefined && Number.isFinite(configured) && configured > 0
106+
? configured
107+
: DEFAULT_MCP_TOOL_TIMEOUT_MS;
101108
return Math.min(max, Math.max(1, Math.floor(raw)));
102109
}
103110

0 commit comments

Comments
 (0)