Skip to content

moveTasks only moves first task when multiple task IDs provided #310

@gnapse

Description

@gnapse

Bug description

The moveTasks function only successfully moves the first task when multiple task IDs are provided in the ids array. Subsequent tasks in the array are not moved to the target destination (project, section, or parent task), despite the API call appearing to succeed.

Expected behaviour

When calling moveTasks(['task1', 'task2', 'task3'], { sectionId: 'target-section' }), all three tasks should be moved to the target section.

Is reproducible

Yes

To reproduce

  1. Create multiple tasks in a Todoist account
  2. Call the moveTasks function with an array containing 2 or more task IDs
  3. Provide move arguments (e.g., { sectionId: 'target-section-id' })
  4. Check the target destination after the API call completes
  5. Observe that only the first task in the array has been moved

Minimal reproduction script:

const { TodoistApi } = require('@doist/todoist-api-typescript');

async function reproduceMoveBug() {
    const api = new TodoistApi('YOUR_API_TOKEN');
    
    // Replace with actual task IDs and section ID from your account
    const taskIds = ['TASK_ID_1', 'TASK_ID_2'];
    const targetSectionId = 'TARGET_SECTION_ID';
    
    console.log(`Moving ${taskIds.length} tasks to section: ${targetSectionId}`);
    
    // Call moveTasks - this will appear to succeed
    const movedTasks = await api.moveTasks(taskIds, { sectionId: targetSectionId });
    console.log(`API returned ${movedTasks.length} moved tasks`);
    
    // Verify by checking what's actually in the target section
    const tasksInSection = await api.getTasks({ sectionId: targetSectionId });
    const foundTaskIds = tasksInSection.results
        .filter(task => taskIds.includes(task.id))
        .map(task => task.id);
    
    console.log(`Expected to find: ${taskIds.length} tasks`);
    console.log(`Actually found: ${foundTaskIds.length} tasks`);
    console.log(`Found task IDs: [${foundTaskIds.join(', ')}]`);
    
    if (foundTaskIds.length < taskIds.length) {
        console.log('❌ BUG CONFIRMED: Not all tasks were moved!');
        const missingTasks = taskIds.filter(id => !foundTaskIds.includes(id));
        console.log(`Missing tasks: [${missingTasks.join(', ')}]`);
    } else {
        console.log('✅ All tasks moved successfully');
    }
}

reproduceMoveBug().catch(console.error);

Expected vs Actual behavior:

  • Expected: Both tasks appear in the target section
  • Actual: Only the first task (TASK_ID_1) appears in the target section

Steps taken to try to reproduce

  • Created integration test script that calls moveTasks with multiple task IDs
  • Verified tasks exist and are accessible via the API
  • Confirmed API call returns successfully with all expected task objects
  • Queried target section after move operation to verify actual task locations
  • Tested with different move targets (projects, sections, parent tasks)

Screenshots

Not applicable - this is an API behavior issue.

Version information:

  • Package version: 5.1.1

Additional information

Root Cause: The issue is in the moveTasks function at line 312-315 in src/TodoistApi.ts. The function generates a single UUID and reuses it for all commands:

const uuid = uuidv4()  // Single UUID generated once
const commands: Command[] = ids.map((id) => ({
    type: 'item_move',
    uuid,  // Same UUID reused for all commands
    args: { ... }
}))

According to the Todoist sync API specification, each command requires a unique UUID. When the same UUID is used for multiple commands, the API treats them as duplicates and only processes the first one.

Fix: Generate a unique UUID for each command:

const commands: Command[] = ids.map((id) => ({
    type: 'item_move',
    uuid: uuidv4(),  // Unique UUID for each command
    args: { ... }
}))

Testing: This issue can be verified with an integration test that:

  1. Calls moveTasks with multiple task IDs
  2. Queries the target destination to confirm all tasks were actually moved
  3. Unit tests alone cannot catch this bug as they mock the API responses

The fix has been tested and confirmed to resolve the issue with all tasks being successfully moved when multiple IDs are provided.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions