Textualize/textual · error · HistoryException

Cannot add an edit to history before it has been performed u

Error message

Cannot add an edit to history before it has been performed using `Edit.do`.

What it means

History.record raises HistoryException when asked to record an Edit that has not been performed yet — the edit carries no _edit_result because Edit.do was never called. History only accepts edits that actually happened, so it can store the replaced text for undo.

Source

Thrown at src/textual/document/_history.py:78

        - The checkpoint timer expires.
        - The maximum number of characters permitted in a checkpoint is reached.
        - A redo is performed (we should not add new edits to a batch that has been redone).
        - The programmer has requested a new batch via a call to `force_new_batch`.
            - e.g. the TextArea widget may call this method in some circumstances.
            - Clicking to move the cursor elsewhere in the document should create a new batch.
            - Movement of the cursor via a keyboard action that is NOT an edit.
            - Blurring the TextArea creates a new checkpoint.
        - The current edit involves a deletion/replacement and the previous edit did not.
        - The current edit is a pure insertion and the previous edit was not.
        - The edit involves insertion or deletion of one or more newline characters.
        - An edit which inserts more than a single character (a paste) gets an isolated batch.

        Args:
            edit: The edit to record.
        """
        edit_result = edit._edit_result
        if edit_result is None:
            raise HistoryException(
                "Cannot add an edit to history before it has been performed using `Edit.do`."
            )

        if edit.text == "" and edit_result.replaced_text == "":
            return None

        is_replacement = bool(edit_result.replaced_text)
        undo_stack = self._undo_stack
        current_time = self._get_time()
        edit_characters = len(edit.text)
        contains_newline = "\n" in edit.text or "\n" in edit_result.replaced_text

        # Determine whether to create a new batch, or add to the latest batch.
        if (
            not undo_stack
            or self._force_end_batch
            or edit_characters > 1
            or contains_newline

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Call edit.do(document) before history.record(edit)
  2. Or use the higher-level Document.edit API, which performs and records atomically
  3. Skip recording for no-op edits (record already ignores empty edits after do)

Example fix

# before
edit = Edit(from_loc, to_loc, text)
history.record(edit)  # raises
# after
edit = Edit(from_loc, to_loc, text)
edit.do(document)
history.record(edit)
Defensive patterns

Strategy: validation

Validate before calling

def perform_and_record(history, edit, document):
    if edit._edit_result is None:
        edit.do(document)
    history.record(edit)

Try / catch

from textual.document._history import HistoryException
try:
    history.record(edit)
except HistoryException:
    edit.do(document)
    history.record(edit)

Prevention

When it happens

Trigger: Creating an Edit(edit) and passing it to history.record(edit) directly without first calling edit.do(document). Typically hit when writing custom edit flows in TextArea/document code.

Common situations: Custom undo/redo integrations or scripted transformations where the developer builds the edit and records it manually, forgetting the do step.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/d497aec378624ef8. Report an issue: GitHub.