Skip to content

Commit a9b78d1

Browse files
fix(tools): clear loading promise on loader rejection to prevent wedged tools
Move loading=null to finally block so transient loader failures don't permanently brick the lazy wrapper. Adds recovery test. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 9054849 commit a9b78d1

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

src/tools/lazy-tool-wrapper.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,45 @@ describe("createLazyTool", () => {
184184
expect(loader).toHaveBeenCalledTimes(1)
185185
expect(onFirstLoad).toHaveBeenCalledTimes(1)
186186
})
187+
188+
it("recovers from loader rejection and allows retry", async () => {
189+
// #given
190+
let callCount = 0
191+
const mockTool: ToolDefinition = {
192+
description: "mock tool",
193+
args: mockToolArgs,
194+
execute: async () => "recovered result",
195+
} as ToolDefinition
196+
const loader = mock(async () => {
197+
callCount++
198+
if (callCount === 1) {
199+
throw new Error("transient failure")
200+
}
201+
return mockTool
202+
})
203+
const tool = createLazyTool({
204+
name: "lazy_mock",
205+
description: "lazy description",
206+
args: mockToolArgs,
207+
loader,
208+
})
209+
210+
// #when: first call fails
211+
let error: Error | undefined
212+
try {
213+
await tool.execute(mockArgs, mockContext)
214+
} catch (err) {
215+
error = err as Error
216+
}
217+
218+
// #then: error surfaced
219+
expect(error?.message).toBe("transient failure")
220+
221+
// #when: retry succeeds because loading was cleared
222+
const result = await tool.execute(mockArgs, mockContext)
223+
224+
// #then
225+
expect(result).toBe("recovered result")
226+
expect(loader).toHaveBeenCalledTimes(2)
227+
})
187228
})

src/tools/lazy-tool-wrapper.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ export function createLazyTool(opts: LazyToolOptions): ToolDefinition {
2727

2828
loading = (async () => {
2929
const start = performance.now()
30-
const tool = await opts.loader()
31-
const elapsed = performance.now() - start
32-
cached = tool
33-
loading = null
34-
opts.onFirstLoad?.(opts.name, elapsed)
35-
return tool
30+
try {
31+
const tool = await opts.loader()
32+
const elapsed = performance.now() - start
33+
cached = tool
34+
opts.onFirstLoad?.(opts.name, elapsed)
35+
return tool
36+
} finally {
37+
loading = null
38+
}
3639
})()
3740

3841
return loading

0 commit comments

Comments
 (0)