True doesn’t create a call and goals registered with undo/1 are executed on the next call port. So, the undo happens, but not in the test body context but right after getting back into the test wrapper where the exception is no longer caught.
B.t.w. when I was documenting this I wanted to refer to the SICStus compatibility. It appears SICStus dropped (deprecated?) undo/1 from SICStus 4. Going through some of the comments about using undo/1 in a constraint context it seems SICStus did actually call the goal while unwinding the trail. That is quite different from SWI-Prolog at the moment: the goal is copied, scheduling its execution for later. That limits its usability to dealing with otherwise non-backtrackable data such as the clause DB, foreign structures, external databases, etc.
I don’t really see the point why one would want undo/1 to poke around in the already backtrackable constraint store. Maybe it made/makes sense in SICStus. Executing the goal in the middle of the trail rewinding is interesting as it avoids copying. I have little clue on how to implement that though
That seems to me the most useful purpose. Like you, I don’t see why would it be used with already backtrackable structures, but I don’t have deep knowledge about these matters…