Todoist Cleanup - Batch Review
SkillProductivityUse when David wants to clean up, review, or triage his Todoist tasks. Batch review process - 4 tasks at a time, David decides, AI executes.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Todoist Cleanup - Batch Review skill
What this skill tells your AI
The instructions your AI receives, as published by matyasstoch/david-skills in skills/organize-tasks/SKILL.md and read by ahel’s review.
Goal: Review every active task with David. He decides - AI presents and executes.
How David Uses Todoist
The Inbox is a brain dump - tasks, ideas, frustrations, observations, notes all land here. This is by design, not a problem. Triage is where signal gets separated from noise. Never comment on this pattern or suggest changing it.
Rules
- No bulk-deleting, auto-triaging, or judgment calls. AI has <1% of context.
- Present tasks exactly as written. David says keep, delete, complete, move, or delegate.
- TXT reminders: When converting a Todoist task to a
.txtreminder file, ALWAYS delete the Todoist task immediately after confirming the file was created. One atomic operation.- Business reminders ->
business/reminders/ - Personal reminders ->
personal/reminders/
- Business reminders ->
Steps
Step 1: Confirm scope
Fetch scope is defined by David at session start. Ask if unclear. Examples:
- "Inbox only" -> project_id
<INBOX_PROJECT_ID>, no date filter. - "Today across 4 main projects" -> Personal, Business, Wisdom, David, due == today.
- "Today + overdue everywhere" -> all 5 projects, due <= today.
Project IDs and the fetch pattern live in the todoist skill. Never assume scope - confirm first.
Track a session set of task contents David has said "keep/skip" on - these stay in Todoist but should not be re-presented.
Step 2: Fetch + present 4
Fetch the scope from Todoist. Filter out anything in the session's kept/skipped set. Sort: P4 first, then P3, P2, P1.
Show progress (handled this session / total remaining in scope), then the current batch (numbered 1-4), a separator, then a preview of the next batch (numbered 5-8). Use identical formatting for both - David reads them side by side and any styling difference makes the preview harder to scan:
**12 handled, 28 remaining**
**Current batch:**
1. **P4** task text
2. **P2** task text
3. **P1** task text
4. **P4** task text
---
**Preview of next batch:**
5. **P4** task text
6. **P3** task text
7. **P2** task text
8. **P1** task text
- Restart numbering from 1 each batch; preview continues from 5-8.
- No project name. Priority + task text only.
- David responds only to 1-4. Preview is for him to start thinking about the next batch in parallel.
Step 3: Execute David's calls
For each of the 4, David gives a disposition. Execute immediately in Todoist:
- Keep / Skip: No Todoist changes. Add task content to session kept/skipped set.
- Delete: Delete from Todoist.
- Complete: Close task in Todoist.
- Move: Move to target project in Todoist.
- Delegate: Move to team member's project.
- Reprioritize: Update priority in Todoist.
- Set due date: Update due date in Todoist.
- Convert to reminder: Create
.txtfile, delete Todoist task. - Add label: Update labels in Todoist (e.g.
"labels": ["reminder"]). Common label:reminderfor self-nudge tasks. - Compound: David often gives multiple actions at once, e.g. "into Business, p2, today" or "into Personal, p3, today, label 'reminder'" = move + set priority + set due date + add label. Execute all parts together - update priority, due date, and labels first, then move (move changes the task ID).
Step 4: Re-fetch and advance
Once all 4 are handled, re-fetch the scope from Todoist (fresh state, latest IDs), filter out the kept/skipped set, present the next 4 plus preview. No confirmation needed. Repeat until no tasks remain in scope.
ALWAYS re-fetch before every batch - David often edits Todoist directly in the web/mobile app between batches (adds, deletes, reprioritizes, reschedules). Never present from cached data. One fresh fetch per batch is non-negotiable.
Step 5: Wrap up
Summarize totals (kept, deleted, completed, moved, delegated). Report final task count remaining in Todoist.
Execution Discipline (LEARNED THE HARD WAY - READ EVERY TIME)
Every operation MUST go through a script file (write_file to
/tmp/todoist_*.sh, then bash). NEVER run inline curl/jq in the terminal.
-
Auth-token masking corrupts inline commands. A security filter rewrites the literal string
Authorization: Bearer $TOKENand can silently break the command. Workaround that survives the filter:TK=$(grep -E '^TODOIST_API' .env | cut -d= -f2- | tr -d '"' | tr -d "'" | tr -d ' ') WORD="Bea""rer" HDR="Authorization: ${WORD} ${TK}" curl -s ... -H "$HDR" ...After write_file, read_file the script to confirm the auth line is intact.
-
curl exits 0 even on 401/auth failure. Capture the response body and check it with
jq. A success body has the task object; a failure has an error/null. Never report a disposition as done without seeing the updated object. -
Don't invent priority errors. P3 = API value 2. If David says P3 and you send
priority:2, that is correct. Re-check the Priority Trap table before fixing anything.
Content Matching with Special Characters
David's brain-dump tasks often contain smart quotes, em-dashes, and ellipses.
Exact full-content jq matching is fragile. Match on a safe ASCII prefix with
startswith, not the full string.
findid() { echo "$INBOX" | jq -r --arg s "$1" '.results[] | select(.content | startswith($s)) | .id' | head -1; }
ID=$(findid "Team member is right")
ID=$(findid "list out all things I")
Recurring Tasks via due_string
When David asks for a recurring disposition ("every 14 days", "every 4 days"),
set it through due_string, NOT a separate field. Combine recurrence + start
date in one string:
"due_string":"every 14 days starting Monday"-> recurs, first due Monday"due_string":"every 10 days starting today"-> recurs, first due today"due_string":"every 4 days starting today"-> recurs from today
Verify the response body's .due.string echoes the recurrence and .due.date
is the correct first occurrence. This is part of a compound action - set
priority/labels/due_string before the move.
Priority Mapping (THE PRIORITY TRAP)
API priority is inverted vs UI. Always convert:
API priority | UI Label | Meaning |
|---|---|---|
| 4 | P1 | Highest (red) |
| 3 | P2 | High (orange) |
| 2 | P3 | Medium (blue) |
| 1 | P4 | Lowest (no color) |
Before presenting ANY batch: Verify the API value matches the UI label.
Recurring Tasks (due_string syntax)
David frequently sets recurring reminders during triage. Set recurrence via the
due_string field on the update call:
"due_string":"every 4 days starting today"-> first occurrence today"due_string":"every 14 days starting Monday"-> first occurrence next Monday"due_string":"every 6 days"-> Todoist defaults first occurrence to today
When David says "every N days, due date today" he means recurrence every N days,
first occurrence today -> "every N days starting today".
Task IDs Change When Moved
When a task is moved to a different project, Todoist assigns a new task ID.
Match by content (task text), not by id.
Execution order for compound actions: Always update priority and due date before moving. The move changes the task ID, so later updates using the old ID will silently fail.
Team-Prefixed Tasks in Inbox
When David asks to auto-categorize remaining Inbox tasks between main projects,
tasks prefixed with a team member's name (e.g. assistant -- ...) are not candidates
for self-categorization - they belong delegated to that person's project. If
David's stated task count does not match the actual Inbox count, the diff is
usually team-prefixed tasks.
Name-only-not-prefixed tasks (e.g. "assistant errand") are different. They are not a team-prefixed delegation pattern. If David scopes categorization to Personal/Business/Wisdom only, ask: "Assistant's project, or Personal?"
Stale ID Trap
ALWAYS re-fetch task IDs immediately before any delete/move/update operation. Never reuse IDs from an earlier fetch - they go stale when tasks are moved or recreated. The API returns 204 on already-deleted IDs with no error, so stale IDs fail silently.
Resuming an Interrupted Batch
If a compound-action run gets interrupted mid-batch, do not re-execute the whole list.
- Re-fetch the source project fresh.
- For each task on the original job list, try content-match against the fresh source fetch.
- NOT FOUND on the source means likely already moved successfully. Verify by fetching the intended target project and confirming expected priority and due date.
- Only re-execute jobs where the task is still in the source project.
This avoids double-moving, duplicate creates, or silently leaving the half-finished task in the wrong state.
API Reference
Uses the todoist skill for all API operations. Key endpoints:
- Delete:
DELETE /api/v1/tasks/{id} - Complete:
POST /api/v1/tasks/{id}/close - Move:
POST /api/v1/tasks/{id}/movewith{"project_id": "TARGET_ID"} - Update priority:
POST /api/v1/tasks/{id}with{"priority": API_VALUE} - Recurring delete: Clear recurrence first (
{"due_string": ""}), then delete. - NEVER use update endpoint to move - it silently ignores
project_id.
Auth: $TODOIST_API_TOKEN from .env file. Always source .env first.
Signals
- GitHub stars
- 59
- Forks
- 11
- Last commit
- Jun 2026
Advanced
- Catalog kind
- skill
- Gateway key
organize-tasks- Source
- github.com/matyasstoch/david-skills