taskwarrior icon indicating copy to clipboard operation
taskwarrior copied to clipboard

simple on-modify hook blocks undoing add task / task cannot understand itself's `{}`

Open marcosrdac opened this issue 3 years ago • 7 comments

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

marcosrdac avatar Jun 01 '22 01:06 marcosrdac

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"

marcosrdac avatar Jun 01 '22 01:06 marcosrdac

the on-modify hook is run when undo command is used?

smemsh avatar Jun 05 '22 13:06 smemsh

@smemsh Exactly.

marcosrdac avatar Jun 07 '22 00:06 marcosrdac

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

smemsh avatar Jun 08 '22 05:06 smemsh

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

domalex avatar Mar 26 '23 16:03 domalex

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.

dathanb avatar Mar 26 '23 17:03 dathanb

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?

areynoua avatar Jan 26 '24 10:01 areynoua