todoist-sync

Dev task tracker synced to Todoist. Auto-triggered via hooks on SessionStart and Stop in configured projects. Fetches due tasks, completes sub-tasks after work, creates chunks from plans. Use /todoist-sync:setup to configure a project.

todoist-sync

Keeps Todoist in sync with Claude Code sessions. Uses mcp__todoist__* MCP tools.

Step 0: Load config

Before doing anything, read the config file. The path ${CLAUDE_PLUGIN_DATA} is substituted by Claude Code:

cat "${CLAUDE_PLUGIN_DATA}/config.json"

If the file doesn't exist, tell the user to run /todoist-sync:setup and stop.

Parse the JSON to find the current repo's project entry. Match git rev-parse --show-toplevel against the repo field in each project. Extract section_id, section_name, and preferences.summary_max_tasks.

On Session Start

The SessionStart hook injects context with the pre-resolved section ID, max tasks, branch, and session file path. All discovery is already done — do NOT call get-overview, search, find-projects, or find-sections.

Exactly one MCP call:

mcp__todoist__find-tasks({ sectionId: "<from hook context>", limit: <from hook context> })

Then from the results:

  1. Filter to overdue + due this week
  2. Show brief summary (5-10 lines max):
    • Overdue tasks (flag these)
    • Due today / this week
    • Total open chunks vs sub-tasks
  3. If a task description mentions a branch, note if it matches current branch

Never search for the project or section by name. The hook already resolved it.

During Session

Completing sub-tasks

When a piece of work is done (code written, tests pass, PR merged):

  1. Identify which Todoist sub-tasks were addressed
  2. Complete them via mcp__todoist__complete-tasks
  3. Update session state — add completed task IDs to active_task_ids in the session file provided by the hook context. Use atomic write to prevent corruption:
    python3 -c "
    import json, sys, os, tempfile
    path = sys.argv[1]
    new_ids = sys.argv[2:]
    with open(path) as f:
        state = json.load(f)
    state['active_task_ids'] = list(set(state.get('active_task_ids', []) + new_ids))
    fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
    with os.fdopen(fd, 'w') as f:
        json.dump(state, f, indent=2)
        f.flush()
        os.fsync(f.fileno())
    os.replace(tmp, path)
    " "SESSION_FILE_FROM_HOOK_CONTEXT" "TASK_ID_1" "TASK_ID_2"
    
    Replace SESSION_FILE_FROM_HOOK_CONTEXT with the actual path from the SessionStart hook context.
  4. If all sub-tasks of a parent chunk are done, ask if the chunk should be completed too

Updating task descriptions

When significant progress is made, append session context to the parent chunk's description via mcp__todoist__update-tasks:

---
**Session: YYYY-MM-DD**
Branch: `branch-name`
Done: what was accomplished
Next: what remains

Append, don't replace existing description content.

On Session Stop

The Stop hook injects context with: project key, section name, branch, active task IDs, session file path.

When you receive this context:

  1. If active_task_ids is empty, skip — nothing was tracked this session
  2. If tasks were tracked, briefly note what was completed
  3. Offer to update any parent chunk descriptions with session context
  4. Do NOT do a full section scan — only act on the active task IDs

Creating Tasks from Plans

When a plan is written (plan mode exit) in a configured project:

  1. Offer to create matching Todoist tasks (don't auto-create without asking)
  2. Each major plan step becomes a chunk (parent task) in the project's section
  3. Each sub-step becomes a sub-task under the chunk
  4. Apply labels based on content:
    • quick-win: <30 min, targeted fix, single-file change
    • blocked: depends on external API, waiting on someone, infra not ready
    • needs-design: UI work, architecture decision, multiple valid approaches
  5. Set priority:
    • p1: blocking other work
    • p2: due this week or next deliverable
    • p3: planned but not urgent
    • p4: backlog / nice-to-have
  6. Search before creating — use mcp__todoist__find-tasks with searchText to avoid duplicates

Task Naming Rules

  • Chunks: verb + deliverable — "Build FastAPI backend", "Fix coverage page bug", "Merge Phase 2 branches"
  • Sub-tasks: specific action — "Add test for endpoint", "Wire service to x_api.py", "Run full test suite"
  • Never vague: no "Work on X", "Look into Y", "Investigate Z" — always specific and completable

What Claude Manages vs User Manages

Claude managesUser manages (phone/Todoist app)
Creating chunks + sub-tasks from plansDue dates and deadlines
Completing sub-tasks after workReordering priorities
Applying labels based on contextMoving tasks between sections
Updating descriptions with session stateRecurring task schedules
Suggesting new tasks from TODOs/FIXMEsDeleting tasks they don't want

Rules

  1. Never create duplicate tasks. Search first via mcp__todoist__find-tasks.
  2. Never set due dates unless the user explicitly asks or the plan has a deadline.
  3. Never delete tasks. Only complete them or update them.
  4. Keep it lightweight. Only sync at session boundaries and plan exits.
  5. Descriptions are breadcrumbs. Branch names, state, context — so the user can pick up later.
  6. Sub-tasks are karma generators. 3-6 per chunk. Each should feel like progress when checked off.