@@ -349,6 +349,43 @@ describe("gateToolCall", () => {
349349 expect ( records [ 0 ] ?. outcome ) . toBe ( "auto-deny" ) ;
350350 } ) ;
351351
352+ test ( "colliding reused call.id does not inherit allow onto different args" , async ( ) => {
353+ const dir = mkdtempSync ( join ( tmpdir ( ) , "approval-log-reactor-" ) ) ;
354+ const cwd = mkdtempSync ( join ( tmpdir ( ) , "gate-cwd-" ) ) ;
355+ const gate = createPermissionGate ( {
356+ approvals : [ { tool : "run_shell" , pattern : "echo hello" } ] ,
357+ interactive : true ,
358+ skipPermissions : false ,
359+ reactorGated : true ,
360+ auto : true ,
361+ cwd,
362+ approvalLog : createApprovalLog ( dir ) ,
363+ requestApproval : async ( ) => {
364+ throw new Error ( "requestApproval must not be invoked under reactor gating" ) ;
365+ } ,
366+ } ) ;
367+ const granted : ToolCall = {
368+ id : "codex-proxy" ,
369+ name : "run_shell" ,
370+ arguments : { command : "echo hello" } ,
371+ } ;
372+ const inner : ToolCall = {
373+ id : "codex-proxy" ,
374+ name : "run_shell" ,
375+ arguments : { command : "echo x | tee src/a.ts" } ,
376+ } ;
377+ expect ( ( await gate . authorizeCall ( granted ) ) . effect ) . toBe ( "allow" ) ;
378+ const { next, wasCalled } = trackingNext ( ) ;
379+ const result = await gateToolCall ( gate , inner , new AbortController ( ) . signal , next ) ;
380+ expect ( result . isError ) . toBe ( true ) ;
381+ expect ( result . content ) . toContain ( BLOCKED_BY_POLICY_PREFIX ) ;
382+ expect ( wasCalled ( ) ) . toBe ( false ) ;
383+ await new Promise ( ( r ) => setTimeout ( r , 10 ) ) ;
384+ const records = readApprovalRecords ( dir ) ;
385+ expect ( records ) . toHaveLength ( 1 ) ;
386+ expect ( records [ 0 ] ?. outcome ) . toBe ( "auto-deny" ) ;
387+ } ) ;
388+
352389 test ( "authorizeCall apply_patch then nested posix with reused id each record" , async ( ) => {
353390 const dir = mkdtempSync ( join ( tmpdir ( ) , "approval-log-reactor-" ) ) ;
354391 const cwd = mkdtempSync ( join ( tmpdir ( ) , "gate-cwd-" ) ) ;
0 commit comments