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():
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.
Anki 2.1.65 (aa9a734f) Python 3.9.15 Qt 6.5.0 PyQt 6.5.0
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>
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
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
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:
if config['ignore_new_cards'] is True and card.type <= TYPE_LEARNING:
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:
parent=cast(QWidget, reviewer.web), op=lambda col: act_on_card(col, card)
lambda out: notify(action_msg(agains, passed)) if out else None
elif config['again_notify'] is True:
notify(info_msg(card, agains, passed))
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']):
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)
elif config.action == Action.Suspend:
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.
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.
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.