{"record":{"id":"4601eb1175c5638e","repo":"stride3d/stride","slug":"unable-to-rollback-this-method-cannot-be-invoked-when","errorCode":null,"errorMessage":"Unable to rollback. This method cannot be invoked when CanRollback is false.","messagePattern":"Unable to rollback\\. This method cannot be invoked when CanRollback is false\\.","errorType":"exception","errorClass":"TransactionException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Design/Transactions/TransactionStack.cs","lineNumber":221,"sourceCode":"                }\n\n                // Complete parent transactions\n                if ((transaction.Flags & TransactionFlags.KeepParentsAlive) != 0)\n                {\n                    foreach (var parentTransaction in transactionsInProgress.Reverse())\n                        parentTransaction.Complete();\n                }\n            }\n        }\n    }\n\n    /// <inheritdoc/>\n    public void Rollback()\n    {\n        lock (lockObject)\n        {\n            if (!CanRollback)\n                throw new TransactionException(\"Unable to rollback. This method cannot be invoked when CanRollback is false.\");\n            if (RollInProgress)\n                throw new TransactionException(\"Unable to rollback. A rollback or rollforward operation is already in progress.\");\n            if (transactionsInProgress.Count > 0)\n                throw new TransactionException(\"Unable to rollback. A transaction is in progress.\");\n\n            var lastTransaction = transactions[--currentPosition];\n            RollInProgress = true;\n            try\n            {\n                lastTransaction.Interface.Rollback();\n            }\n            finally\n            {\n                RollInProgress = false;\n            }\n            TransactionRollbacked?.Invoke(this, new TransactionEventArgs(lastTransaction));\n        }\n    }","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs#L203-L239","documentation":"TransactionStack.Rollback() can only undo a transaction when CanRollback is true, which requires the undo position (currentPosition) to be greater than 0. This error is thrown when Rollback is called with nothing left to undo.","triggerScenarios":"Calling transactionStack.Rollback() when currentPosition == 0 (no transactions have been executed, or all were already undone).","commonSituations":"Undo button/action invoked repeatedly until history is exhausted; calling Rollback on a freshly created or just-Clear()ed stack; multiple Rollback calls racing so the last one finds an empty undo range.","solutions":["Check transactionStack.CanRollback before calling Rollback.","Disable/gray out the Undo action when CanRollback is false (subscribe to property-change updates).","Update UI state after each Rollback so the guard reflects the new position.","Catch TransactionException around Rollback as a defensive fallback for concurrent undo triggers."],"exampleFix":"// before\nstack.Rollback();\n\n// after\nif (stack.CanRollback)\n    stack.Rollback();","handlingStrategy":"validation","validationCode":"if (transactionStack.CanRollback)\n    transactionStack.Rollback();","typeGuard":"bool CanUndo(TransactionStack stack) => stack.CanRollback;","tryCatchPattern":"try\n{\n    transactionStack.Rollback();\n}\ncatch (TransactionException ex) when (ex.Message.Contains(\"CanRollback is false\"))\n{\n    // nothing to undo; update UI state\n}","preventionTips":["Gate undo commands on CanRollback.","Subscribe to CanRollback change notifications to enable/disable UI.","Refresh guards after every rollback/clear."],"tags":["transactions","undo-redo","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}