# Target undo op not found

**URL:** <https://forums.ankiweb.net/t/target-undo-op-not-found/33622>\
**Category:** Development\
**Created:** [August 26, 2023, 12:55pm UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622 "2023-08-26T12:55:54Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 26, 2023, 12:55pm UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/1 "2023-08-26T12:55:54Z")

</div>

I use `undo_entry = mw.col.undo_status().last_step` to get the undo entry of answer card for `merge_undo_entries(undo_entry)`. But it will cause an error when other add-on call `add_custom_undo_entry()`:

```auto
Error
An error occurred. Please start Anki while holding down the shift key, which will temporarily disable the add-ons you have installed.
If the issue only occurs when add-ons are enabled, please use the Tools > Add-ons menu item to disable some add-ons and restart Anki, repeating until you discover the add-on that is causing the problem.
When you've discovered the add-on that is causing the problem, please report the issue to the add-on author.
Debug info:
Anki 2.1.65 (aa9a734f) Python 3.9.15 Qt 6.5.0 PyQt 6.5.0
Platform: macOS-13.3.1-arm64-arm-64bit
Flags: frz=True ao=True sv=3
Add-ons, last update check: 2023-08-26 16:56:26
Add-ons possibly involved: <U+2068>FSRS4Anki Helper<U+2069>

Caught exception:
Traceback (most recent call last):
  File "aqt.taskman", line 122, in _on_closures_pending
  File "aqt.taskman", line 71, in <lambda>
  File "aqt.taskman", line 90, in wrapped_done
  File "aqt.operations", line 125, in wrapped_done
  File "aqt.reviewer", line 460, in after_answer
  File "aqt.reviewer", line 473, in _after_answering
  File "_aqt.hooks", line 3982, in __call__
  File "/Users/jarrettye/Library/Application Support/Anki2/addons21/759844606/schedule/ __init__.py", line 8, in reschedule_and_disperse_siblings_when_review
    undo_entry = reschedule_when_review(reviewer, card, ease)
  File "/Users/jarrettye/Library/Application Support/Anki2/addons21/759844606/schedule/reschedule.py", line 381, in reschedule_when_review
    mw.col.merge_undo_entries(undo_entry)
  File "anki.collection", line 1054, in merge_undo_entries
  File "anki._backend_generated", line 1823, in merge_undo_entries
  File "anki._backend", line 156, in _run_command
anki.errors.InvalidInput: target undo op not found

```

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 26, 2023, 1:01pm UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/2 "2023-08-26T13:01:57Z")

</div>

I find that `merge_undo_entries` in another add-on will modify `last_step`. It induces the error.

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 27, 2023, 6:15am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/3 "2023-08-27T06:15:27Z")

</div>

Are both your add-on and their add-on calling these routines inside a CollectionOp?

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 27, 2023, 6:45am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/4 "2023-08-27T06:45:46Z")

</div>

My add-on doesn’t use collection op. I have two feature appended to the hook of reviewer\_did\_answer\_card. And I want to merge them into Answer Card (Anki build-in undo entry).

> <https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/18cfb88c22989fd0b52cd577019aefde1e1b821a/schedule/reschedule.py#L348-L382>

> <https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/18cfb88c22989fd0b52cd577019aefde1e1b821a/schedule/disperse_siblings.py#L273-L316>

> <https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/18cfb88c22989fd0b52cd577019aefde1e1b821a/schedule/ __init__.py#L7-L13>

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 27, 2023, 6:55am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/5 "2023-08-27T06:55:19Z")

</div>

Here is the code of another add-on:

```python
def on_did_answer_card(reviewer: Reviewer, card: Card, ease: Literal[1, 2, 3, 4]) -> None:
    """Bury card if it was answered 'again' too many times within the specified time."""

    # only care about failed cards
    if ease != 1:
        return

    if config['ignore_new_cards'] is True and card.type <= TYPE_LEARNING:
        return

    agains = agains_in_the_timeframe(card.id)
    passed = time_passed().hours()

    if agains >= threshold(card):
        if config['tag'] or config['flag'] or config.action != Action.No:
            CollectionOp(
                parent=cast(QWidget, reviewer.web), op=lambda col: act_on_card(col, card)
            ).success(
                lambda out: notify(action_msg(agains, passed)) if out else None
            ).run_in_background()
    elif config['again_notify'] is True:
        notify(info_msg(card, agains, passed))

def init():
    gui_hooks.reviewer_did_answer_card.append(on_did_answer_card)

```

```python
def act_on_card(col: Collection, card: Card) -> ResultWithChanges:
    pos = col.add_custom_undo_entry("Mortician: modify difficult card")

    if config['tag'] and not (note := card.note()).has_tag(config['tag']):
        note.add_tag(config['tag'])
        col.update_note(note)

    if Color.No != config.flag != Color(card.user_flag()):
        col.set_user_flag_for_cards(config.flag.value, cids=[card.id,])

    if config.action == Action.Bury:
        col.sched.bury_cards(ids=[card.id,], manual=False)
        sched_reset(col)
    elif config.action == Action.Suspend:
        col.sched.suspend_cards(ids=[card.id,])
        sched_reset(col)

    return col.merge_undo_entries(pos)

```

[https://ankiweb.net/shared/info/1255924302](https://ankiweb.net/shared/info/1255924302)

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 27, 2023, 7:04am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/6 "2023-08-27T07:04:05Z")

</div>

IIRC CollectionOps are serialized, so if you wrap your code in it, it should prevent other operations from interleaving with yours. That will also avoid hanging the UI if it takes too long.

Please note that changing the cards like this during review (whether in a CollectionOp or not) is going to waste a fair few CPU cycles and lead to slower display of the next card, as it will cause the queues to be rebuilt after every card is answered. A more efficient approach would be to do this processing in bulk, when the user is not in the middle of reviews.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 27, 2023, 7:44am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/7 "2023-08-27T07:44:55Z")

</div>

> [@dae](#):
>
> IIRC CollectionOps are serialized, so if you wrap your code in it, it should prevent other operations from interleaving with yours.

So is it possible to merge my operations to the last other’s undo entry?

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 28, 2023, 4:10am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/8 "2023-08-28T04:10:22Z")

</div>

If the other add-on executed first and they merged their changes with the review entry, then your code should merge the changes with the review entry too.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 4:33am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/9 "2023-08-28T04:33:15Z")

</div>

But my add-on doesn’t know the action executed by other add-ons. They could use add\_custom\_undo\_entry or merge into the col.undo\_status().last\_step.

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 28, 2023, 4:58am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/10 "2023-08-28T04:58:41Z")

</div>

True, but if they’re occurring at the time you run the hook, chances are they’re also using the hook, and if so, they should probably also be merging their changes into it, or it will make it hard for the user to undo reviews.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 7:03am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/11 "2023-08-28T07:03:16Z")

</div>

Fine. I have rewritten my code and use add\_custom\_undo\_entry().

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 28, 2023, 7:08am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/12 "2023-08-28T07:08:22Z")

</div>

Sorry, I think we may have crossed-wires here. If you want to continue making changes on every review, I was suggesting you continue using your existing approach (merging into whatever happened before), as chances are that’ll either be the last review, or another add-on that (should have) modified it.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 7:22am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/13 "2023-08-28T07:22:06Z")

</div>

> [@dae](#):
>
> as chances are that’ll either be the last review, or another add-on that (should have) modified it.

My original approach works in the fronter case, but causes error in the latter case. That is the main topic in this post.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 7:29am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/14 "2023-08-28T07:29:46Z")

</div>

For example, I find that these code will induce `target undo op not found` after user presses the rating button:

```python
@reviewer_did_answer_card.append
def feature_one():
    undo_entry = mw.col.undo_status().last_step
    ......
    mw.col.merge_undo_entries(undo_entry)

@reviewer_did_answer_card.append
def feature_two():
    undo_entry = mw.col.undo_status().last_step
    ......
    mw.col.merge_undo_entries(undo_entry)

```

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 28, 2023, 7:38am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/15 "2023-08-28T07:38:47Z")

</div>

What happens when you run that code in a CollectionOp?

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 7:58am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/16 "2023-08-28T07:58:44Z")

</div>

The Error will be displayed in a popup

![image](https://us1.discourse-cdn.com/flex002/uploads/anki2/original/2X/5/57d2211d766e2f9fae2a2ac7aa4876f33f8f7538.png)

---

<div class="post-metadata">

**Author:** ![dae](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/dae/32/65_2.png) [@dae](https://forums.ankiweb.net/u/dae)\
**Post date:** [August 28, 2023, 8:00am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/17 "2023-08-28T08:00:58Z")

</div>

Can you link to the code you’re testing with?

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 8:10am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/18 "2023-08-28T08:10:28Z")

</div>

> <https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/440591835bdd76dc9ba2facbf305008745d19df1/schedule/reschedule.py#L348-L353>

> <https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/440591835bdd76dc9ba2facbf305008745d19df1/schedule/disperse_siblings.py#L268-L271>

 ![image](https://us1.discourse-cdn.com/flex002/uploads/anki2/original/2X/c/ccea741169b9fcee8127451500c8b26d45ac5a35.jpeg)

This popup occurs less frequently then before.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 8:12am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/19 "2023-08-28T08:12:27Z")

</div>

I’m trying to make a minimal reproducible case.

---

<div class="post-metadata">

**Author:** ![L.M.Sherlock](https://sea2.discourse-cdn.com/flex002/user_avatar/forums.ankiweb.net/l.m.sherlock/32/16770_2.png) [@L.M.Sherlock](https://forums.ankiweb.net/u/L.M.Sherlock)\
**Post date:** [August 28, 2023, 8:23am UTC](https://forums.ankiweb.net/t/target-undo-op-not-found/33622/20 "2023-08-28T08:23:20Z")

</div>

Collection for reproduction: [reproduction.colpkg - Google Drive](https://drive.google.com/file/d/1YZHviqbzYdzVh6iQ7Q1-BJ8H_iS366k3/view?usp=sharing)

Steps to reproduce the error:

1. install [AJT Mortician](https://ankiweb.net/shared/info/1255924302) and [fsrs4anki-helper.ankiaddon - Google Drive](https://drive.google.com/file/d/1u3T2ucZulPRJelsAgM2sBqfZBCmkaAex/view?usp=sharing)

2. enable auto-disperse and auto-reschedule

 ![image](https://us1.discourse-cdn.com/flex002/uploads/anki2/original/2X/2/24964e3c07eabc735e5132c277bffdfdba32447d.jpeg)

1. enable auto-bury

 ![image](https://us1.discourse-cdn.com/flex002/uploads/anki2/original/2X/0/00c232ec16aecb209e25b88dfeffd3ef1c16478c.png)

1. enter into Default deck and press again:

 ![image](https://us1.discourse-cdn.com/flex002/uploads/anki2/original/2X/3/332c2d6002f7fad63ba36a977524cc0166e9f0b2.png)

[Next page](https://forums.ankiweb.net/t/target-undo-op-not-found/33622.md?page=2)
