dotnet/wpf · error · InvalidOperationException

SR.UndoNoOpenUnit

Error message

SR.UndoNoOpenUnit

What it means

InvalidOperationException from UndoManager.Close: there is no currently open parent undo unit to close — Close was called without a matching prior Open (or the unit was already closed), so the close operation has no target.

Solutions

  1. Check UndoManager.OpenedUnit != null before calling Close
  2. Ensure each Open has exactly one Close (use try/finally with a closed flag)
  3. Skip Close when Open was never reached or failed

Example fix

// before
undoManager.Open(unit);
...
undoManager.Close(unit, UndoCloseAction.Commit);
undoManager.Close(unit, UndoCloseAction.Commit); // throws
// after
undoManager.Open(unit);
...
if (undoManager.OpenedUnit != null)
{
    undoManager.Close(unit, UndoCloseAction.Commit);
}
Defensive patterns

Strategy: validation

Validate before calling

if (undoManager.OpenedUnit == null) { /* nothing open: skip Close */ }

Try / catch

try { undoManager.Close(unit, UndoCloseAction.Commit); } catch (InvalidOperationException ex) when (ex.Message.Contains("no open unit")) { /* nothing to close: ignore */ }

Prevention

When it happens

Trigger: Calling Close without a matching Open, calling Close twice for the same unit, or Close after an exception path already closed the unit.

Common situations: Double-close from both a text-change handler and a finally block, or Close in cleanup code when Open was skipped because undo was disabled.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/2678dc56be2d0c31. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/UndoManager.cs:322

        /// Thrown if:
        ///     UndoManager is disabled
        ///     no undo unit is currently open
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Thrown if unit is null
        /// </exception>
        internal void Close(IParentUndoUnit unit, UndoCloseAction closeAction)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

            if (OpenedUnit == null)
            {
                throw new InvalidOperationException(SR.UndoNoOpenUnit);
            }

            // find the parent of the given unit
            if (OpenedUnit != unit)
            {
                IParentUndoUnit closeParent;

                closeParent = OpenedUnit;

                while (closeParent.OpenedUnit != null && closeParent.OpenedUnit != unit)
                {
                    closeParent = closeParent.OpenedUnit;
                }

                if (closeParent.OpenedUnit == null)
                {
                    throw new ArgumentException(SR.UndoUnitNotFound, nameof(unit));
                }

View on GitHub (pinned to 81131a70a4)