I wrote such a test and noticed that file descriptors were being closed each time ZSH_XTRACEFD was (re)assigned, even as a local variable. So I removed the code lines closing the previous file descriptor in xtracefdsetfn, and it seemed to work well. However it seems that leaving the scope of a function where ZSH_XTRACEFD was assigned as a local variable does not unset it, and therefore leaves the file descriptor opened. I thought leaving the scope of a function would implicitly "unset" all variables local to this scope, but it seems it's not the case. I'm not sure how to fix that. How to make sure the file descriptor of a local ZSH_XTRACEFD is closed when leaving the function scope? Or, how to make sure a local ZSH_XTRACEFD is unset when leaving the function scope? Or, how to call xtracefdunsetfn when leaving the function scope? I pasted below the test that should pass if the local file descriptor was properly closed. rm -f redir A() { local ZSH_XTRACEFD=5 B print 'Function A to file descriptor 5' unset ZSH_XTRACEFD print 'Function A to file descriptor 2' } B() { local ZSH_XTRACEFD=6 print 'Function B to file descriptor 6' } exec 4>redir4 5>redir5 6>redir6 ZSH_XTRACEFD=4 set -x print 'Main to file descriptor 4' A ZSH_XTRACEFD=5 ZSH_XTRACEFD=6 unset ZSH_XTRACEFD set +x cat redir4 print cat redir5 print cat redir6 0:Scoped ZSH_XTRACEFD correctly set and restored >Main to file descriptor 4 >Function B to file descriptor 6 >Function A to file descriptor 5 >Function A to file descriptor 2 >+(eval):16> print 'Main to file descriptor 4' >+(eval):17> A >+A:1> local ZSH_XTRACEFD=5 >+(eval):18> ZSH_XTRACEFD=5 >+(eval):19> ZSH_XTRACEFD=6 >+(eval):20> unset ZSH_XTRACEFD > >+A:2> B >+B:1> local ZSH_XTRACEFD=6 >+A:3> print 'Function A to file descriptor 5' >+A:4> unset ZSH_XTRACEFD > >+B:2> print 'Function B to file descriptor 6' ?+A:5> print 'Function A to file descriptor 2' ?(eval):18: file descriptor 5 is not valid ?(eval):19: file descriptor 6 is not valid ?+(eval):21> set +x