{"record":{"id":"c83c3a9caac7ac86","repo":"stride3d/stride","slug":"unable-to-rollforward-this-method-cannot-be-invoked-when","errorCode":null,"errorMessage":"Unable to rollforward. This method cannot be invoked when CanRollforward is false.","messagePattern":"Unable to rollforward\\. This method cannot be invoked when CanRollforward is false\\.","errorType":"exception","errorClass":"TransactionException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Design/Transactions/TransactionStack.cs","lineNumber":247,"sourceCode":"            try\n            {\n                lastTransaction.Interface.Rollback();\n            }\n            finally\n            {\n                RollInProgress = false;\n            }\n            TransactionRollbacked?.Invoke(this, new TransactionEventArgs(lastTransaction));\n        }\n    }\n\n    /// <inheritdoc/>\n    public void Rollforward()\n    {\n        lock (lockObject)\n        {\n            if (!CanRollforward)\n                throw new TransactionException(\"Unable to rollforward. This method cannot be invoked when CanRollforward is false.\");\n            if (RollInProgress)\n                throw new TransactionException(\"Unable to rollforward. 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.Rollforward();\n            }\n            finally\n            {\n                RollInProgress = false;\n            }\n            TransactionRollforwarded?.Invoke(this, new TransactionEventArgs(lastTransaction));\n        }\n    }","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs#L229-L265","documentation":"TransactionStack.Rollforward() can only redo a transaction when CanRollforward is true, which requires the redo position (currentPosition) to be below the top of the history. This error is thrown when Rollforward is called with nothing left to redo.","triggerScenarios":"Calling transactionStack.Rollforward() when currentPosition is at the end of the transactions list (no undone transactions to redo, or history was cleared).","commonSituations":"Redo button/action invoked when there is nothing to redo; calling Rollforward after new operations reset the redo range; calling it on a fresh or Clear()ed stack.","solutions":["Check transactionStack.CanRollforward before calling Rollforward.","Disable/gray out the Redo action when CanRollforward is false (listen to property-change updates).","Update UI state after each Rollback/Rollforward so the guard stays accurate.","Catch TransactionException around Rollforward as a defensive fallback for concurrent redo triggers."],"exampleFix":"// before\nstack.Rollforward();\n\n// after\nif (stack.CanRollforward)\n    stack.Rollforward();","handlingStrategy":"validation","validationCode":"if (transactionStack.CanRollforward)\n    transactionStack.Rollforward();","typeGuard":"bool CanRedo(TransactionStack stack) => stack.CanRollforward;","tryCatchPattern":"try\n{\n    transactionStack.Rollforward();\n}\ncatch (TransactionException ex) when (ex.Message.Contains(\"CanRollforward is false\"))\n{\n    // nothing to redo; update UI state\n}","preventionTips":["Gate redo commands on CanRollforward.","Subscribe to CanRollforward change notifications to enable/disable UI.","Refresh guards after every rollback/rollforward/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"}