Skip to content

Be honest about what undo can undo

Undo is a new operation. Recovery should restore only what recovery safely can, skip records someone edited since, and say what can’t be reversed.

By Daniel Ternyak5 min readSources checked September 28, 2026

An agent tries to move 24 due dates from Thursday to Friday. Two records can’t be updated. A teammate later changes one of the remaining dates to Monday. Another date goes from Friday to Monday and back to Friday.

The user clicks Undo. Which dates should change?

A button labeled Undo hides several decisions: what the original operation actually did, what has happened since, and whose work recovery is allowed to overwrite. When the product can’t answer those questions, “undo” promises more than the product can deliver.

Begin with the effects that remain

Restoring a field and reversing the field’s consequences are different jobs. A due-date change can send a notification, trigger an automation, or make somebody reschedule their own work. Putting the field back to Thursday can’t make those effects un-happen.

The useful distinction is the boundary of the recovery:

Original operationPossible recoveryWhat may remain
Prepare an unsaved editDiscard the proposalThe conversation and review history
Update a due dateRestore the previous date, if nothing changed sinceNotifications, dependent changes, and later human decisions
Create a draft in another appRemove or archive the draft, if allowedOutside history, references, and copies
Send an emailSend a correctionThe original message, its recipients, and what they did next

The rows are examples with conditions, not a universal ranking. A supposedly private draft may have been shared. An apparently local edit may already have synced elsewhere. Describe the effects the recovery can actually reverse.

Approval should name the work recovery will refer to

Before talking about undo, make the original operation concrete. A search for “unfinished tasks in this project” isn’t the same thing as the 24 records that matched when the person reviewed the change. A twenty-fifth task could appear while the approval waits.

There are two reasonable policies. Freeze the reviewed list and leave out later matches. Or refresh the proposal and ask the person to review the new records. Either policy works when the interface explains the policy. Quietly adding an unreviewed record to the run makes recovery harder before any write happens.

The proposal should record each task’s ID, old date, intended date, and version. A new version of the proposal needs a new approval. The new version doesn’t inherit approval just because the title still says “Move dates to Friday.”

The operation needs a stable ID too, so a person returning after a timeout can retrieve the original result instead of applying the change again to today’s records.

A current value is not a complete history

The easy conflict is visible. The agent wrote Friday, a teammate wrote Monday, and recovery should keep the teammate’s later decision. Comparing the current date with Friday catches the conflict.

The harder case looks unchanged:

EventDateRecord version
Original stateThursday1
Agent updateFriday2
Teammate changes the planMonday3
Teammate changes it backFriday4

A check on the value alone sees Friday and may overwrite the teammate’s newest decision with Thursday. A check on the version sees 4, not the agent’s 2, and stops automatic restoration for that record.

The policy is intentionally cautious. Another product might track versions per field, so an unrelated title edit doesn’t block date recovery. Either way, the comparison has to represent the changes that matter. HTTP’s If-Match is one standard building block: the server refuses an update when the resource’s current version doesn’t match the version the client supplied. RFC 9110, section 13.1.1

The check and the write must also happen together. Reading the current version, deciding the write is safe, and then writing unconditionally leaves a gap in which another edit can arrive.

Partial success needs two receipts

An execution receipt should account for every attempted record. Successful updates need the previous value, the written value, and the written version. Locked, deleted, and conflicting records need an explicit outcome even though nothing changed.

Recovery starts from the successful writes in that receipt and produces a result of its own. A task skipped during execution isn’t a failed restoration: the operation never changed the task, so there’s nothing to restore.

The distinction should survive the summary a person sees. Say a refreshed proposal covered 25 tasks: 23 dates changed, and 21 can be restored after two newer edits. “Restored 21 dates; 2 have newer edits” tells a different story from “21 of 25 restored.” The second version makes four records sound like failures of undo, though two never changed. Link both receipts so a returning user can see the full account.

Repeating a recovery should return the original recovery receipt too. When a teammate edits a restored record afterward, a retry of the old recovery must not reverse the teammate’s new work. A fresh, authorized correction is a different operation.

Reverting the agent is a separate operation

A product can keep versions of an agent’s instructions, tools, and settings. Restoring those settings changes how future work runs. Restoring settings doesn’t, by itself, reverse the work the agent already produced.

Notion documents restoring a Custom Agent to a past configuration. Restoring settings is configuration recovery. Notion’s docs don’t say that pages, messages, or outside objects from earlier runs revert along with the settings. Notion’s Custom Agents

Give these actions different names when their effects differ: restore settings, cancel remaining work, restore eligible dates, prepare a correction. One global Undo label makes people discover the difference after they act.

Recovery has authority and limits of its own

The original permission to edit a record may no longer apply. The object may have moved, a connected account may have been revoked, or the person may have lost access. Undo is another write and needs the current permission checks for that write. A receipt is evidence of what happened, not a permanent license to change the object again.

Retention matters too. When previous values are kept for a limited time, show the limit while recovery is still possible. Put the operation and its remaining options in history, not only in a toast that disappears.

For a product review, ask for one concrete answer: after this operation and everything that happened since, what exactly will this recovery change, and what will the recovery leave behind? When the interface can’t show that before the person clicks, the product needs a narrower promise.

The pattern#