My point was `UnboundLocalError` could be resolved by explicitly assigning exception `e` to another variable. Everything else was from @morten_bohne 's original code.
Indeed, the vanilla `pymxs.undo()` absorbs all the unhandled exceptions in the context and makes up everything is fine. All the actions right before the exception will be recorded in undo stack. Something is recorded to the undo stack but no one knows there was a failure. It makes debugging harder. This is a bad design, IMO.
Always calling `pymxs.run_undo()` might be an excessive behavior and someone might want to remove that line. It will be more wise to take over the responsibility of failure handling to the caller, not the context itself (callee didn't know how the handle this and it became "unhandled"). The caller always has a better knowledge of what they doing so they can decide the correct way to react on the failure. To give a chance to the caller to react on the failure, there's need to propagate the exception to the outside of undo context. I think this is the key of the proposed code.
About "canceling the incomplete undo record" rather than "undoing using the incomplete record", unfortunately there's no way to accomplish this, AFAIK. Maxscript has no command to "cancel" the single undo record. `clearUndoBuffer()` will flush all the undo records. That's why `pymxs.run_undo()` after the failure of undo context could be considered as second worst solution. This mitigation will left the re-do record instead of un-do and it is distant from "cancel", but at least it prevents hanging in the middle of failure state, which normal users don't expect.
---
Raising an exception within the pymxs.undo block will 'undo' anything that was created prior the raised exception within the block.
Edit: Oh, I missed this sentence that MAX actually do cancel ;( But propagating exceptions back will be still helpful to the caller to decide post-failure actions.