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