simple on-modify hook blocks undoing add task / task cannot understand itself's `{}`
The problem
I've recently created a hook for adding tags if the task's description is modified. In every other case, It just returns the task as it was.
All is fine except when I try to undo a previous add command. In such case, on-modify will receive two JSONs:
- the previous task:
{"description":"...", ... } - the new task:
{}(an empty JSON because we are undoing add, thus removing the task)
Anyway, when the script prints the new task back to taskwarrior, the software shows me the following error and does not conclude the removal:
Hook Error: Expected 1 JSON task(s), found 0, in hook script: on-modify
Expected behaviour
Task should understand a task JSON that it has itself written ({}) when the on-modify script gives it back to it.
How to reproduce
Add this hook to ~/.task/hooks:
#!/usr/bin/env sh
read old_task
read new_task
echo "$new_task"
Then add a task:
task add my task
Finally, try to undo the addition:
task undo
Debugging
When using debug option (task rc.debug.hooks=2 undo), it shows me:
The last modification was made 2022-05-31
Prior Values Current Values
description my task
entry 20220531
modified 20220531
status pending
uuid 5073c990-9cbd-46bf-963b-157d42b6fcb0
The undo command is not reversible. Are you sure you want to revert to the previous state? (yes/no) yes
Timer Config::load (/home/marcosrdac/.config/task/taskrc) 0.000308 sec
Found hook script /home/marcosrdac/.config/task/hooks/on-modify
Hook: Calling /home/marcosrdac/.config/task/hooks/on-modify
Hook: input
{"description":"my task","entry":"20220601T005720Z","modified":"20220601T005720Z","status":"pending","uuid":"5073c990-9cbd-46bf-963b-157d42b6fcb0"}
{}
Hooks: args
api:2
args:task rc.debug.hooks=2 undo
command:undo
rc:/home/marcosrdac/.config/task/taskrc
data:/home/marcosrdac/dox/.h/taskwarrior
version:2.6.1
Timer Hooks::execute (/home/marcosrdac/.config/task/hooks/on-modify) 0.004553 sec
Hook: output
{}
Hook: Completed with status 0
Hook Error: Expected 1 JSON task(s), found 0, in hook script: on-modify
And exits with error code = 4.
My task diag
task 2.6.1
Platform: Linux
Compiler
Version: 10.3.0
Caps: +stdc +stdc_hosted +LP64 +c8 +i32 +l64 +vp64 +time_t64
Compliance: C++17
Build Features
CMake: 3.21.2
libuuid: libuuid + uuid_unparse_lower
libgnutls: 3.7.2
Build type: Release
Configuration
File: /home/marcosrdac/.config/task/taskrc (found), 1950 bytes, mode 100644
Data: /home/marcosrdac/dox/.h/taskwarrior (found), dir, mode 40755
TASKRC: /home/marcosrdac/.config/task/taskrc
TASKDATA: /home/marcosrdac/dox/.h/taskwarrior
Locking: Enabled
GC: Enabled
$VISUAL: nvim
Server:
CA: -
Certificate: -
Key: -
Trust: strict
Ciphers: NORMAL
Creds:
Hooks
System: Enabled
Location: /home/marcosrdac/.config/task/hooks
Active: on-modify (executable)
Inactive:
Tests
Terminal: 63x44
Dups: Scanned 280 tasks for duplicate UUIDs:
No duplicates found
Broken ref: Scanned 280 tasks for broken references:
No broken references found
For some reason, returning the old task instead of the modified one (as in the wiki) is a current workaround:
#!/usr/bin/env sh
read old_task
read new_task
# workaround for taskwarrior block when undoing a task addition
command=`echo "$3" | cut -d : -f 2`
[ $command = undo ] && [ "$new_task" = '{}' ] && echo "$old_task" && exit 0
echo "$new_task"
the on-modify hook is run when undo command is used?
@smemsh Exactly.
actually, isn't an undo of an add, a delete? which is the same as new being empty, like you're seeing. hmm, but things could have been triggered that are not reversible like that. probably just use rc.hooks=0 with undo, if you know the script doesn't handle that case. not sure if it's possible to differentiate from the script
I am getting the same error when adding a simple task and then want to undo it afterwards:
task undo
The last modification was made 2023-03-26
Prior Values Current Values
description test2
entry 2023-03-26
modified 2023-03-26
project inbox
status pending
uuid 25ff1192-b443-425f-af23-9abc18f4c7c6
The undo command is not reversible. Are you sure you want to revert to the previous
state? (yes/no) y
Hook Error: Expected 1 JSON task(s), found 0, in hook script: on-modify.timewarrior
I have a Python on-modify script that just triggers a notification (so never needs to modify the task it's handed). I have this section to handle task undo after task add:
# parse the input
data = sys.stdin.read().strip()
decoder = json.JSONDecoder()
original_task, offset = decoder.raw_decode(data)
modified_task, offset = decoder.raw_decode(data[offset:].strip())
if len(modified_task) == 0:
# task undo removing a task entirely
print(json.dumps(original_task))
else:
print(json.dumps(modified_task))
And it works:
$ task add foo
Created task 215.
Context 'work' set. Use 'task context none' to remove.
$ task 215
Name Value
ID 215
Description foo
Status Pending
Entered 2023-03-26 10:37:28 (2s)
Last modified 2023-03-26 10:37:28 (2s)
Tags work
Virtual tags LATEST PENDING READY TAGGED UNBLOCKED
UUID e84db7ab-ecb2-4329-ad5f-c9f822408507
Urgency 0.8
tags 0.8 * 1 = 0.8
------
0.8
$ task undo
The last modification was made 2023-03-26
Prior Values Current Values
description foo
entry 2023-03-26
modified 2023-03-26
status pending
tags work
tags_work x
uuid e84db7ab-ecb2-4329-ad5f-c9f822408507
The undo command is not reversible. Are you sure you want to revert to the previous state? (yes/no) yes
Task removed.
$ task 215
No matches.
This bug also frequently happens with the task-relative-recur add-on (because the add-on performs an “add” on task completion, so, to undo a completion, we have to do two undos, but the first undo fails due to this bug).
Summary of previous comments:
As mentioned before, when undoing an add, the on-modify add-ons receive a task as old value and an empty json object / task ({}) as new value. This makes sense.
When an add-on detects it has nothing to do, it returns the new task as received, so, here, the empty task. This makes sense too.
However, the undo command breaks this logic. When it receives an empty object back from an add-on, it says: Hook Error: Expected 1 JSON task(s), found 0. If the add-on returns the old task, the undo works. I don't really see how can the output of the add-on be useful under these conditions.
For now, with api v2, one can check the that one of the parameter of the hook call is command:undo, see marcosrdac's comment.
With the previous api, I think the best would be to guess an undo command from the empty object received, as proposed by dathanb. I think a purge command may produce the same input, but this is unlikely to be a problem because purge was introduced in 2.6.0 and apiv2 in 2.4.0.
However, it would be a good thing to make hooks more consistent, maybe a v3?