This is a complicated scenario with no simple solution -- and what's happening is probably not what you think is happening.
When you click on the "X", the system sends a WM_ENDSESSION to the message handling window that TCC creates for itself (otherwise, like other console apps, TCC would not be able to intercept or respond to those messages). Upon receipt of the WM_ENDSESSION, TCC calls the EXIT command. Your TCEXIT.BTM is opened, and the "SET /U ..." line is executed. That eventually triggers an HWND_BROADCAST / WM_SETTINGCHANGE message, which is required to use the SendMessageTimeout API. (It cannot use something like PostMessage because it has to pass a pointer in LPARAM.) The SendMessageTimeout API is passed a 5-second timeout, and it starts calling all of the top level windows.
Eventually, the TCC window is called -- but it cannot process the message, because it's still in the WM_ENDSESSION message. (If TCC did something like a ReplyMessage() in the WM_ENDSESSION call, TCC would terminate immediately without being able to run TCEXIT.) So Windows waits 5 seconds, decides that TCC isn't going to process the WM_SETTINGCHANGE message, and continues notifying the other top-level windows. At roughly the same time, Windows also decides that TCC isn't going to return from the WM_ENDSESSION, so Windows summarily terminates TCC.
So -- the moral of the story is don't do things in your TCEXIT that require nested message handling in the console window. (Or stop clicking on "X" to close TCC!)
Alternatively, I could reduce the timeout for SendMessageTimeout to something less (one second?), though I suspect you'd then file a bug that TCC takes a second to exit after clicking "X" with a SET /U in TCEXIT.