dotnet/wpf · error · InvalidOperationException

SR.UndoUnitNotOnTopOfStack

Error message

SR.UndoUnitNotOnTopOfStack

What it means

InvalidOperationException from UndoManager.Reopen: the unit passed for reopening is not the top unit on the relevant stack (undo stack in Normal/Redo state, redo stack in Undo state), so it cannot be reopened out of order.

Solutions

  1. Only call Reopen with the unit reference that was most recently closed on top of the stack
  2. Verify UndoCount > 0 and the target unit equals the top of the undo stack before reopening
  3. If the unit is buried in the stack, abandon reopening and open a fresh unit instead

Example fix

// before
undoManager.Reopen(oldUnit);
// after
if (undoManager.UndoCount > 0 && undoManager.PeekUndoStack() == oldUnit)
{
    undoManager.Reopen(oldUnit);
}
Defensive patterns

Strategy: validation

Validate before calling

bool canReopen = undoManager.UndoCount > 0 && undoManager.PeekUndoStack() == unit;

Try / catch

try { undoManager.Reopen(unit); } catch (InvalidOperationException ex) when (ex.Message.Contains("top of stack")) { /* open a fresh unit instead */ }

Prevention

When it happens

Trigger: Reopen called while State is Normal or Redo and either UndoCount == 0 or PeekUndoStack() != unit.

Common situations: Trying to reopen a unit that was already merged/closed into another unit, or reopening after other edits pushed new units onto the undo stack.

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/988f684fd5ec507b. Report an issue: GitHub.

Appendix: source

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

            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

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

            switch (State)
            {
                case UndoState.Normal:
                case UndoState.Redo:
                    {
                        if (UndoCount == 0 || PeekUndoStack() != unit)
                        {
                            throw new InvalidOperationException(SR.UndoUnitNotOnTopOfStack);
                        }

                        break;
                    }

                case UndoState.Undo:
                    {
                        if (RedoStack.Count == 0 || (IParentUndoUnit)RedoStack.Peek() != unit)
                        {
                            throw new InvalidOperationException(SR.UndoUnitNotOnTopOfStack);
                        }

                        break;
                    }

                case UndoState.Rollback:
                default:
                    // should only happen if someone changes the UndoState enum or parameter validation

View on GitHub (pinned to 81131a70a4)