Skip to content

Commit 3c59352

Browse files
committed
Add a timeout for code_search to prevent indefinite hanging
1 parent b6795d3 commit 3c59352

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

npm-app/src/tool-handlers.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,13 @@ export const handleCodeSearch: ToolHandler<'code_search'> = async (
255255
const rgPath = await getRgPath()
256256
const maxResults = parameters.maxResults ?? 15
257257
const globalMaxResults = 250
258+
const timeoutSeconds = 10
258259

259260
return new Promise((resolve) => {
260261
let stdout = ''
261262
let stderr = ''
263+
let timeoutId: NodeJS.Timeout | null = null
264+
let isResolved = false
262265

263266
const basename = path.basename(projectPath)
264267
const pattern = parameters.pattern
@@ -296,6 +299,30 @@ export const handleCodeSearch: ToolHandler<'code_search'> = async (
296299
stdio: ['ignore', 'pipe', 'pipe'],
297300
})
298301

302+
// Set up timeout to kill hung processes
303+
timeoutId = setTimeout(() => {
304+
if (!isResolved) {
305+
isResolved = true
306+
childProcess.kill('SIGTERM')
307+
// Give it a moment to die gracefully, then force kill
308+
setTimeout(() => {
309+
if (!childProcess.killed) {
310+
childProcess.kill('SIGKILL')
311+
}
312+
}, 1000)
313+
resolve([
314+
{
315+
type: 'json',
316+
value: {
317+
errorMessage: `Code search timed out after ${timeoutSeconds} seconds. The search may be too broad or the pattern too complex. Try narrowing your search with more specific flags or a more specific pattern.`,
318+
stdout: stdout ? truncateStringWithMessage({ str: stdout, maxLength: 1000 }) : '',
319+
stderr: stderr ? truncateStringWithMessage({ str: stderr, maxLength: 1000 }) : '',
320+
},
321+
},
322+
])
323+
}
324+
}, timeoutSeconds * 1000)
325+
299326
childProcess.stdout.on('data', (data) => {
300327
stdout += data.toString()
301328
})
@@ -305,6 +332,10 @@ export const handleCodeSearch: ToolHandler<'code_search'> = async (
305332
})
306333

307334
childProcess.on('close', (code) => {
335+
if (isResolved) return
336+
isResolved = true
337+
if (timeoutId) clearTimeout(timeoutId)
338+
308339
const lines = stdout.split('\n').filter((line) => line.trim())
309340

310341
// Group results by file
@@ -458,6 +489,10 @@ export const handleCodeSearch: ToolHandler<'code_search'> = async (
458489
})
459490

460491
childProcess.on('error', (error) => {
492+
if (isResolved) return
493+
isResolved = true
494+
if (timeoutId) clearTimeout(timeoutId)
495+
461496
resolve([
462497
{
463498
type: 'json',

sdk/src/tools/code-search.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function codeSearch({
1414
maxResults = 15,
1515
globalMaxResults = 250,
1616
maxOutputStringLength = 20_000,
17+
timeoutSeconds = 10,
1718
}: {
1819
projectPath: string
1920
pattern: string
@@ -22,10 +23,13 @@ export function codeSearch({
2223
maxResults?: number
2324
globalMaxResults?: number
2425
maxOutputStringLength?: number
26+
timeoutSeconds?: number
2527
}): Promise<CodebuffToolOutput<'code_search'>> {
2628
return new Promise((resolve) => {
2729
let stdout = ''
2830
let stderr = ''
31+
let timeoutId: NodeJS.Timeout | null = null
32+
let isResolved = false
2933

3034
const flagsArray = (flags || '').split(' ').filter(Boolean)
3135
let searchCwd = projectPath
@@ -55,6 +59,34 @@ export function codeSearch({
5559
stdio: ['ignore', 'pipe', 'pipe'],
5660
})
5761

62+
// Set up timeout to kill hung processes
63+
timeoutId = setTimeout(() => {
64+
if (!isResolved) {
65+
isResolved = true
66+
childProcess.kill('SIGTERM')
67+
// Give it a moment to die gracefully, then force kill
68+
setTimeout(() => {
69+
if (!childProcess.killed) {
70+
childProcess.kill('SIGKILL')
71+
}
72+
}, 1000)
73+
74+
const truncatedStdout = stdout.length > 1000 ? stdout.substring(0, 1000) + '\n\n[Output truncated]' : stdout
75+
const truncatedStderr = stderr.length > 1000 ? stderr.substring(0, 1000) + '\n\n[Error output truncated]' : stderr
76+
77+
resolve([
78+
{
79+
type: 'json',
80+
value: {
81+
errorMessage: `Code search timed out after ${timeoutSeconds} seconds. The search may be too broad or the pattern too complex. Try narrowing your search with more specific flags or a more specific pattern.`,
82+
stdout: truncatedStdout,
83+
stderr: truncatedStderr,
84+
},
85+
},
86+
])
87+
}
88+
}, timeoutSeconds * 1000)
89+
5890
childProcess.stdout.on('data', (data) => {
5991
stdout += data.toString()
6092
})
@@ -64,6 +96,10 @@ export function codeSearch({
6496
})
6597

6698
childProcess.on('close', (code) => {
99+
if (isResolved) return
100+
isResolved = true
101+
if (timeoutId) clearTimeout(timeoutId)
102+
67103
const lines = stdout.split('\n').filter((line) => line.trim())
68104

69105
// Group results by file
@@ -207,6 +243,10 @@ export function codeSearch({
207243
})
208244

209245
childProcess.on('error', (error) => {
246+
if (isResolved) return
247+
isResolved = true
248+
if (timeoutId) clearTimeout(timeoutId)
249+
210250
resolve([
211251
{
212252
type: 'json',

0 commit comments

Comments
 (0)