From 5fc04bb9083e33de1af621b9101d87edb24e8e93 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Mon, 23 Feb 2026 04:27:48 -0700 Subject: [PATCH] Avoid timeouts in client integration test fixtures Replace brittle longRunningTools whitelists with a prefix-based check (toolName.startsWith("codeql_")) so that every codeql_* tool gets a 5-minute timeout with progress-based resets. This fixes intermittent -32001 RequestTimeout failures in CI (e.g. codeql_pack_install) caused by cold JVM starts, network pack downloads, and Windows runner overhead. - ql-mcp-client.js: callTool() now checks prefix instead of whitelist - integration-test-runner.js: add callTool() helper with timeout logic; migrate all direct this.client.callTool() calls to use it - monitoring-integration-test-runner.js: add timeout options to callTool() - mcp-test-suite.js: add explicit timeout to connectivity test --- client/src/lib/integration-test-runner.js | 91 +++++++------------ client/src/lib/mcp-test-suite.js | 9 +- .../lib/monitoring-integration-test-runner.js | 20 +++- client/src/ql-mcp-client.js | 21 ++--- 4 files changed, 64 insertions(+), 77 deletions(-) diff --git a/client/src/lib/integration-test-runner.js b/client/src/lib/integration-test-runner.js index 7631ee21..4789e6b2 100644 --- a/client/src/lib/integration-test-runner.js +++ b/client/src/lib/integration-test-runner.js @@ -60,6 +60,27 @@ export class IntegrationTestRunner { this.options = options; } + /** + * Call an MCP tool with an appropriate timeout. + * + * All codeql_* tools invoke the CodeQL CLI or language server JVM, which + * can be slow in CI (cold JVM start, network pack downloads, Windows + * runner overhead). A generous 5-minute timeout avoids intermittent + * -32001 RequestTimeout failures. + */ + async callTool(toolName, args) { + const isCodeQLTool = toolName.startsWith("codeql_"); + const requestOptions = { + timeout: isCodeQLTool ? 300000 : 60000, + resetTimeoutOnProgress: isCodeQLTool + }; + return await this.client.callTool( + { name: toolName, arguments: args }, + undefined, + requestOptions + ); + } + /** * Run integration tests for a specific tool */ @@ -268,12 +289,9 @@ export class IntegrationTestRunner { } // Run the tool - const result = await this.client.callTool({ - name: toolName, - arguments: { - "files": files, - "in-place": true - } + const result = await this.callTool(toolName, { + "files": files, + "in-place": true }); this.logger.log(`Tool ${toolName} result: ${result.content?.[0]?.text || "No output"}`); @@ -337,11 +355,8 @@ export class IntegrationTestRunner { const _afterContent = fs.readFileSync(afterPath, "utf8"); // Run the codeql_lsp_diagnostics tool on the before content - const result = await this.client.callTool({ - name: toolName, - arguments: { - ql_code: beforeContent - } + const result = await this.callTool(toolName, { + ql_code: beforeContent }); this.logger.log( @@ -472,10 +487,7 @@ export class IntegrationTestRunner { resolvePathPlaceholders(testConfig.arguments, this.logger); // Run the tool with custom arguments - const result = await this.client.callTool({ - name: toolName, - arguments: testConfig.arguments - }); + const result = await this.callTool(toolName, testConfig.arguments); this.logger.log(`Tool ${toolName} result: ${result.content?.[0]?.text || "No output"}`); @@ -659,10 +671,7 @@ export class IntegrationTestRunner { // Check if qlpack.yml exists and install dependencies if (fs.existsSync(path.join(packDir, "codeql-pack.yml"))) { try { - await this.client.callTool({ - name: "codeql_pack_install", - arguments: { packDir: packDir } - }); + await this.callTool("codeql_pack_install", { packDir: packDir }); } catch (installError) { this.logger.log( ` Warning: Could not install pack dependencies: ${installError.message}` @@ -708,9 +717,8 @@ export class IntegrationTestRunner { if (fs.existsSync(absoluteTestSourceDir)) { this.logger.log(`Database not found, extracting from ${testSourceDir}`); - const extractResult = await this.client.callTool({ - name: "codeql_test_extract", - arguments: { tests: [testSourceDir] } + const extractResult = await this.callTool("codeql_test_extract", { + tests: [testSourceDir] }); if (extractResult.isError) { const errorText = extractResult.content?.[0]?.text || "Unknown error"; @@ -739,38 +747,10 @@ export class IntegrationTestRunner { params = await this.getToolSpecificParams(toolName, testCase); } - // Call the tool with appropriate parameters - // Set extended timeout for long-running operations - const longRunningTools = [ - "codeql_database_analyze", - "codeql_database_create", - "codeql_lsp_completion", - "codeql_lsp_definition", - "codeql_lsp_diagnostics", - "codeql_lsp_references", - "codeql_query_run", - "codeql_test_run" - ]; - - const requestOptions = longRunningTools.includes(toolName) - ? { - timeout: 300000, // 5 minutes for long-running tools - resetTimeoutOnProgress: true - } - : { - timeout: 60000 // 60 seconds for other tools - }; - - this.logger.log(`Calling tool ${toolName} with timeout: ${requestOptions.timeout}ms`); + // Call the tool with appropriate parameters (timeout is handled by this.callTool) + this.logger.log(`Calling tool ${toolName}`); - const result = await this.client.callTool( - { - name: toolName, - arguments: params - }, - undefined, - requestOptions - ); + const result = await this.callTool(toolName, params); // For monitoring tests, we primarily check if the tool executed successfully // Special handling for session management tools that expect sessions to exist @@ -979,9 +959,8 @@ export class IntegrationTestRunner { if (!fs.existsSync(databaseDir) && fs.existsSync(testDir)) { this.logger.log(`Database not found for query run, extracting first: ${databaseDir}`); // Call codeql test extract to create the database - const extractResult = await this.client.callTool({ - name: "codeql_test_extract", - arguments: { tests: [testDir] } + const extractResult = await this.callTool("codeql_test_extract", { + tests: [testDir] }); if (extractResult.isError) { throw new Error(`Failed to extract database: ${extractResult.content[0].text}`); diff --git a/client/src/lib/mcp-test-suite.js b/client/src/lib/mcp-test-suite.js index d477ae6b..c883f9d9 100644 --- a/client/src/lib/mcp-test-suite.js +++ b/client/src/lib/mcp-test-suite.js @@ -32,10 +32,11 @@ export class MCPTestSuite { this.logger.log("Testing tool execution..."); // Try to call a simple resolve languages tool - const result = await this.client.callTool({ - name: "codeql_resolve_languages", - arguments: {} - }); + const result = await this.client.callTool( + { name: "codeql_resolve_languages", arguments: {} }, + undefined, + { timeout: 300000, resetTimeoutOnProgress: true } + ); this.logger.log(`Tool result: ${JSON.stringify(result, null, 2)}`); diff --git a/client/src/lib/monitoring-integration-test-runner.js b/client/src/lib/monitoring-integration-test-runner.js index 8634dbaf..15da9bc0 100644 --- a/client/src/lib/monitoring-integration-test-runner.js +++ b/client/src/lib/monitoring-integration-test-runner.js @@ -17,13 +17,23 @@ export class MonitoringIntegrationTestRunner { } /** - * Helper method to call MCP tools with correct format + * Helper method to call MCP tools with correct format and timeout. + * + * All codeql_* tools invoke the CodeQL CLI or language server JVM, which + * can be slow in CI. A generous 5-minute timeout avoids intermittent + * -32001 RequestTimeout failures. */ async callTool(toolName, parameters = {}) { - return await this.client.callTool({ - name: toolName, - arguments: parameters - }); + const isCodeQLTool = toolName.startsWith("codeql_"); + const requestOptions = { + timeout: isCodeQLTool ? 300000 : 60000, + resetTimeoutOnProgress: isCodeQLTool + }; + return await this.client.callTool( + { name: toolName, arguments: parameters }, + undefined, + requestOptions + ); } /** diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index cb4ca905..c00cfe49 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -49,20 +49,17 @@ class CodeQLMCPClient { * Helper method to call MCP tools with correct format */ async callTool(toolName, parameters = {}, options = {}) { - // Set appropriate timeout based on tool type - // Long-running tools like query_run and test_run need extended timeouts - const longRunningTools = [ - "codeql_query_run", - "codeql_test_run", - "codeql_database_analyze", - "codeql_database_create" - ]; + // All codeql_* tools invoke the CodeQL CLI or language server JVM, which + // can be slow in CI (cold JVM start, network pack downloads, Windows + // runner overhead). Use a generous 5-minute timeout for every CodeQL + // tool to avoid intermittent -32001 RequestTimeout failures. + const isCodeQLTool = toolName.startsWith("codeql_"); const defaultOptions = { - // Use 5 minute timeout for long-running tools, 60 seconds for others - timeout: longRunningTools.includes(toolName) ? 300000 : 60000, - // Reset timeout on progress notifications for long-running operations - resetTimeoutOnProgress: longRunningTools.includes(toolName) + // Use 5 minute timeout for CodeQL tools, 60 seconds for others + timeout: isCodeQLTool ? 300000 : 60000, + // Reset timeout on progress notifications for CodeQL operations + resetTimeoutOnProgress: isCodeQLTool }; const requestOptions = { ...defaultOptions, ...options };