Gitlab issues to Todoist

Describe the issue/error/question

I am trying to sync Gitlab issues with Todoist tasks.

  • Create Gitlab issue → Todoist create task works.
  • Close Gitlab issue → Close Todoist task also works

However since there is no update API for Todoist, once a Gitlab issue is created, I attach the Gitlab project ID to the Todoist task description. Then when a Gitlab issue gets updated, I retrieve all Todoist tasks, search their description for the relevant Gitlab ID, and once I get those I delete them (if for some reason/ problem there are multiple Todoist tasks created) and create a new one with the updated info.

My problem is that updating a Gitlab issue sometimes results in double Todoist task IDs and I would love some feedback from you to fix it and ideally refactor my workflow to make it more efficient.

The workflow I currently use

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        460,
        -20
      ]
    },
    {
      "parameters": {
        "owner": "",
        "repository": "",
        "events": [
          "issues"
        ]
      },
      "name": "Gitlab Trigger",
      "type": "n8n-nodes-base.gitlabTrigger",
      "position": [
        60,
        80
      ],
      "webhookId": "459081ce-f254-44b2-925a-a078007f36c5",
      "typeVersion": 1,
      "credentials": {
        "gitlabApi": {
          "id": "3",
          "name": "Gitlab account 2"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "return item;"
      },
      "name": "the_gitlab_event",
      "type": "n8n-nodes-base.functionItem",
      "typeVersion": 1,
      "position": [
        360,
        360
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"action\"]}}",
              "operation": "contains",
              "value2": "open"
            }
          ]
        }
      },
      "name": "action_is_open_reopen",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        540,
        700
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"action\"]}}",
              "value2": "close"
            }
          ]
        }
      },
      "name": "action_is_close",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        540,
        180
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "project": 2290715561,
        "content": "=[{{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"title\"]}}]({{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"url\"]}})",
        "options": {
          "description": "=gitlab_issue_id: {{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"id\"]}}",
          "dueDateTime": "={{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"due_date\"]}}"
        }
      },
      "name": "create_task",
      "type": "n8n-nodes-base.todoist",
      "position": [
        1700,
        580
      ],
      "typeVersion": 1,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "delete",
        "taskId": "={{$node[\"filter_todoist_tasks_by_gitlab_issue_id\"].json[\"id\"]}}"
      },
      "name": "delete_task",
      "type": "n8n-nodes-base.todoist",
      "position": [
        1700,
        380
      ],
      "typeVersion": 1,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "close",
        "taskId": "={{$node[\"filter_todoist_tasks_by_gitlab_issue_id1\"].json[\"id\"]}}"
      },
      "name": "close_task",
      "type": "n8n-nodes-base.todoist",
      "position": [
        1580,
        -80
      ],
      "typeVersion": 1,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "owner": "",
        "repository": "",
        "events": [
          "issues"
        ]
      },
      "name": "Gitlab Trigger1",
      "type": "n8n-nodes-base.gitlabTrigger",
      "position": [
        60,
        360
      ],
      "webhookId": "fe3eac42-fba2-43fe-b30b-2f37bcef3e38",
      "typeVersion": 1,
      "credentials": {
        "gitlabApi": {
          "id": "3",
          "name": "Gitlab account 2"
        }
      }
    },
    {
      "parameters": {
        "owner": "",
        "repository": "",
        "events": [
          "issues"
        ]
      },
      "name": "Gitlab Trigger2",
      "type": "n8n-nodes-base.gitlabTrigger",
      "position": [
        60,
        640
      ],
      "webhookId": "319272f8-6260-4a95-a390-5ef55557d254",
      "typeVersion": 1,
      "credentials": {
        "gitlabApi": {
          "id": "3",
          "name": "Gitlab account 2"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"the_gitlab_event\"].json[\"body\"][\"object_attributes\"][\"action\"]}}",
              "value2": "update"
            }
          ]
        }
      },
      "name": "action_is_update",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        540,
        360
      ]
    },
    {
      "parameters": {
        "functionCode": "return [{json: {empty: items.length == 1 && Object.keys(items[0].json).length == 0}}];"
      },
      "name": "tasks_found",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1140,
        60
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json[\"empty\"]}}",
              "operation": "notEqual",
              "value2": true
            }
          ],
          "number": []
        }
      },
      "name": "task_exists",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1320,
        60
      ]
    },
    {
      "parameters": {
        "functionCode": "return [{json: {empty: items.length == 1 && Object.keys(items[0].json).length == 0}}];"
      },
      "name": "tasks_found1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1160,
        260
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "projectId": 2290715561
        }
      },
      "name": "get_all_open_tasks",
      "type": "n8n-nodes-base.todoist",
      "position": [
        800,
        340
      ],
      "typeVersion": 1,
      "alwaysOutputData": true,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json[\"empty\"]}}",
              "operation": "notEqual",
              "value2": true
            }
          ],
          "number": []
        }
      },
      "name": "task_exists1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1380,
        260
      ]
    },
    {
      "parameters": {
        "functionCode": "const filtered_tasks = [];\nfor (item of items) {\n  if (item.json.description.includes(\"gitlab_issue_id: \" + $node[\"the_gitlab_event\"].json.body.object_attributes.id)) {\n    filtered_tasks.push(item);\n  }\n}\nreturn filtered_tasks;"
      },
      "name": "filter_todoist_tasks_by_gitlab_issue_id",
      "type": "n8n-nodes-base.function",
      "position": [
        980,
        260
      ],
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "projectId": 2290715561
        }
      },
      "name": "get_all_open_tasks1",
      "type": "n8n-nodes-base.todoist",
      "position": [
        800,
        120
      ],
      "typeVersion": 1,
      "alwaysOutputData": true,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "const filtered_tasks = [];\nfor (item of items) {\n  if (item.json.description.includes(\"gitlab_issue_id: \" + $node[\"the_gitlab_event\"].json.body.object_attributes.id)) {\n    filtered_tasks.push(item);\n  }\n}\nreturn filtered_tasks;"
      },
      "name": "filter_todoist_tasks_by_gitlab_issue_id1",
      "type": "n8n-nodes-base.function",
      "position": [
        980,
        80
      ],
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "projectId": 2290715561
        }
      },
      "name": "get_all_open_tasks2",
      "type": "n8n-nodes-base.todoist",
      "position": [
        800,
        720
      ],
      "typeVersion": 1,
      "alwaysOutputData": true,
      "credentials": {
        "todoistOAuth2Api": {
          "id": "4",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "const filtered_tasks = [];\nfor (item of items) {\n  if (item.json.description.includes(\"gitlab_issue_id: \" + $node[\"the_gitlab_event\"].json.body.object_attributes.id)) {\n    filtered_tasks.push(item);\n  }\n}\nreturn filtered_tasks;"
      },
      "name": "filter_todoist_tasks_by_gitlab_issue_id2",
      "type": "n8n-nodes-base.function",
      "position": [
        980,
        720
      ],
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "functionCode": "return [{json: {empty: items.length == 1 && Object.keys(items[0].json).length == 0}}];"
      },
      "name": "tasks_found2",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1160,
        720
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json[\"empty\"]}}",
              "operation": "notEqual",
              "value2": true
            }
          ],
          "number": []
        }
      },
      "name": "task_exists2",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1340,
        720
      ]
    }
  ],
  "connections": {
    "Gitlab Trigger": {
      "main": [
        [
          {
            "node": "the_gitlab_event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "the_gitlab_event": {
      "main": [
        [
          {
            "node": "action_is_update",
            "type": "main",
            "index": 0
          },
          {
            "node": "action_is_close",
            "type": "main",
            "index": 0
          },
          {
            "node": "action_is_open_reopen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "action_is_open_reopen": {
      "main": [
        [
          {
            "node": "get_all_open_tasks2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "action_is_close": {
      "main": [
        [
          {
            "node": "get_all_open_tasks1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "delete_task": {
      "main": [
        [
          {
            "node": "get_all_open_tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gitlab Trigger1": {
      "main": [
        [
          {
            "node": "the_gitlab_event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gitlab Trigger2": {
      "main": [
        [
          {
            "node": "the_gitlab_event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "action_is_update": {
      "main": [
        [
          {
            "node": "get_all_open_tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tasks_found": {
      "main": [
        [
          {
            "node": "task_exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "task_exists": {
      "main": [
        [
          {
            "node": "close_task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tasks_found1": {
      "main": [
        [
          {
            "node": "task_exists1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_all_open_tasks": {
      "main": [
        [
          {
            "node": "filter_todoist_tasks_by_gitlab_issue_id",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "task_exists1": {
      "main": [
        [
          {
            "node": "delete_task",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "create_task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "filter_todoist_tasks_by_gitlab_issue_id": {
      "main": [
        [
          {
            "node": "tasks_found1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_all_open_tasks1": {
      "main": [
        [
          {
            "node": "filter_todoist_tasks_by_gitlab_issue_id1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "filter_todoist_tasks_by_gitlab_issue_id1": {
      "main": [
        [
          {
            "node": "tasks_found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_all_open_tasks2": {
      "main": [
        [
          {
            "node": "filter_todoist_tasks_by_gitlab_issue_id2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "filter_todoist_tasks_by_gitlab_issue_id2": {
      "main": [
        [
          {
            "node": "tasks_found2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tasks_found2": {
      "main": [
        [
          {
            "node": "task_exists2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "task_exists2": {
      "main": [
        null,
        [
          {
            "node": "create_task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
```

Hey @boris_runakov, I am sorry to hear you’re having trouble here.

Unfortunately I am not a todoist user myself, so would need a bit of help with the terminology here. When you say “updating a Gitlab issue sometimes results in double Todoist task IDs”, does this mean your create_task node runs twice for one trigger event?

Hello there!
Yes this is what happens.
If I update an issue on Gitlab:
image

two tasks are created on Todoist:
image

What I expected, is get all tasks from Todoist , with gitlab_issue_id the issue id that was updated on Gitlab, delete those, and create one new Todoist task from scratch with the updated info.

Additionally I now noticed that sometimes, there are two tasks created also on open_reopen branch. And this does not happen regularly, but only sometimes.
Hope this helped :slight_smile:

Thanks @boris_runakov. Is there a chance your Gitlab triggers run twice for an issue you create in Gitlab? How do your execution logs look like? Are there multiple executions for a single issue or does your “create_task” node perhaps run twice for a single trigger execution?

You re right @MutedJam. Upon create there are two executions performed. However one Todoist task is created.
Have to figure out why I get two executions though .