@@ -371,6 +371,116 @@ describe("createCodexDynamicToolBridge", () => {
371371 expect ( badExecute ) . not . toHaveBeenCalled ( ) ;
372372 } ) ;
373373
374+ it ( "quarantines unreadable dynamic tool descriptors without dropping healthy siblings" , ( ) => {
375+ const warn = vi . spyOn ( embeddedAgentLog , "warn" ) . mockImplementation ( ( ) => undefined ) ;
376+ const poisonedName = createTool ( {
377+ name : "fuzzplugin_unreadable_name" ,
378+ execute : vi . fn ( ) ,
379+ } ) ;
380+ Object . defineProperty ( poisonedName , "name" , {
381+ enumerable : true ,
382+ get ( ) {
383+ throw new Error ( "fuzzplugin dynamic tool name getter exploded" ) ;
384+ } ,
385+ } ) ;
386+ const poisonedSchema = createTool ( {
387+ name : "fuzzplugin_unreadable_schema" ,
388+ execute : vi . fn ( ) ,
389+ } ) ;
390+ Object . defineProperty ( poisonedSchema , "parameters" , {
391+ enumerable : true ,
392+ get ( ) {
393+ throw new Error ( "fuzzplugin dynamic tool schema getter exploded" ) ;
394+ } ,
395+ } ) ;
396+ const invalidName = createTool ( {
397+ name : "" ,
398+ execute : vi . fn ( ) ,
399+ } ) ;
400+ const poisonedExecute = createTool ( {
401+ name : "fuzzplugin_unreadable_execute" ,
402+ } ) ;
403+ Object . defineProperty ( poisonedExecute , "execute" , {
404+ enumerable : true ,
405+ get ( ) {
406+ throw new Error ( "fuzzplugin dynamic tool execute getter exploded" ) ;
407+ } ,
408+ } ) ;
409+
410+ const bridge = createCodexDynamicToolBridge ( {
411+ tools : [
412+ poisonedName ,
413+ poisonedSchema ,
414+ invalidName ,
415+ poisonedExecute ,
416+ createTool ( { name : "message" } ) ,
417+ ] ,
418+ signal : new AbortController ( ) . signal ,
419+ } ) ;
420+
421+ expect ( bridge . availableSpecs . map ( ( tool ) => tool . name ) ) . toEqual ( [ "message" ] ) ;
422+ expect ( bridge . specs . map ( ( tool ) => tool . name ) ) . toEqual ( [ "message" ] ) ;
423+ expect ( bridge . telemetry . quarantinedTools ) . toEqual ( [
424+ {
425+ tool : "tool[0]" ,
426+ violations : [ "tool[0].name is unreadable" ] ,
427+ } ,
428+ {
429+ tool : "fuzzplugin_unreadable_schema" ,
430+ violations : [ "fuzzplugin_unreadable_schema.inputSchema is unreadable" ] ,
431+ } ,
432+ {
433+ tool : "tool[2]" ,
434+ violations : [ "tool[2].name must be a non-empty string" ] ,
435+ } ,
436+ {
437+ tool : "fuzzplugin_unreadable_execute" ,
438+ violations : [
439+ "fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks" ,
440+ ] ,
441+ } ,
442+ ] ) ;
443+ expect ( warn ) . toHaveBeenCalledWith (
444+ expect . stringContaining (
445+ "tool[0], fuzzplugin_unreadable_schema, tool[2], fuzzplugin_unreadable_execute" ,
446+ ) ,
447+ expect . objectContaining ( {
448+ tools : [
449+ {
450+ tool : "tool[0]" ,
451+ violations : [ "tool[0].name is unreadable" ] ,
452+ } ,
453+ {
454+ tool : "fuzzplugin_unreadable_schema" ,
455+ violations : [ "fuzzplugin_unreadable_schema.inputSchema is unreadable" ] ,
456+ } ,
457+ {
458+ tool : "tool[2]" ,
459+ violations : [ "tool[2].name must be a non-empty string" ] ,
460+ } ,
461+ {
462+ tool : "fuzzplugin_unreadable_execute" ,
463+ violations : [
464+ "fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks" ,
465+ ] ,
466+ } ,
467+ ] ,
468+ } ) ,
469+ ) ;
470+
471+ const registeredBridge = createCodexDynamicToolBridge ( {
472+ tools : [ poisonedExecute , createTool ( { name : "message" } ) ] ,
473+ registeredTools : [
474+ createTool ( { name : "fuzzplugin_unreadable_execute" } ) ,
475+ createTool ( { name : "message" } ) ,
476+ ] ,
477+ signal : new AbortController ( ) . signal ,
478+ } ) ;
479+
480+ expect ( registeredBridge . availableSpecs . map ( ( tool ) => tool . name ) ) . toEqual ( [ "message" ] ) ;
481+ expect ( registeredBridge . specs . map ( ( tool ) => tool . name ) ) . toEqual ( [ "message" ] ) ;
482+ } ) ;
483+
374484 it ( "can expose all dynamic tools directly for compatibility" , ( ) => {
375485 const bridge = createCodexDynamicToolBridge ( {
376486 tools : [ createTool ( { name : "web_search" } ) , createTool ( { name : "message" } ) ] ,
0 commit comments