feat(comment): notify collaborators when adding a comment - #482
Merged
scottlovegrove merged 2 commits intoAug 18, 2026
Conversation
Comments posted with `td` notified nobody. `comment add` never sent uidsToNotify, so a teammate named in a comment found out only if they happened to open the task. That also broke the next comment. Todoist's clients pick the recipients themselves and send them with each comment; the API notifies exactly who it is handed and derives nobody on its own. A reply takes its recipients from the comment before it, so a comment posted with an empty list silences the following comment too -- including one a human writes in the app. `comment add` now takes --notify, accepting names, emails, id:xxx or "me", comma-separated as --labels does. Omitting it mirrors the clients: the assignee, assigner and creator on a task's first comment, or the previous comment's participants on a reply, always excluding the author. --no-notify posts in silence. Naming yourself in --notify is honoured rather than filtered out -- leaving yourself out is only right when the recipients were inferred rather than asked for. Who was notified is now visible: on the confirmation line after adding, on `comment view`, and in plain `--json` rather than only under --full. Refs Doist/todoist-mcp#509
doistbot
reviewed
Aug 18, 2026
doistbot
left a comment
Member
There was a problem hiding this comment.
This PR adds --notify/--no-notify to td comment add so comments can notify collaborators, bringing it to parity with the MCP's notification feature — including sensible defaults that mirror the Todoist clients' recipient inference logic.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (6)
src/commands/comment/add.ts:180:
describeRecipientshere anddescribeNotifiedinsrc/commands/comment/view.ts(line 81) duplicate the same id → short-name map plus raw-id fallback (new Map(collaborators.map(c => [c.id, formatUserShortName(c.name)]))thenuserIds.map(id => names.get(id) ?? id).join(', ')). Pull that mapping into one shared helper (e.g.formatUserIds(collaborators, userIds)insrc/lib/collaborators.ts) and have both callers use it; each file keeps only its own fetch path.src/lib/collaborators.ts:168: This recreates the workspace and shared-project pagination/mapping already implemented by
CollaboratorCache.fetchWorkspaceUsersandfetchProjectCollaborators. Extract the endpoint-specific fetches into shared helpers (or expose a cache load/read API) and have both paths use them, so cursor handling and collaborator normalization have one implementation.src/commands/comment/add.ts:94: When
--projectis set,resolveProjectRefalready returned the full project (it's used fortargetName/targetProjectIdabove), so thisapi.getProject(targetProjectId)is a redundant request. Hoist the resolved project out of the branch and reuse it here instead of refetching.CODEBASE.md:181: Remove the dedicated
comment-recipients.tsentry. This helper currently has one production caller (comment/add.ts), so listing it makes the structural map drift toward a file index rather than its intended catalog of broadly reusable helpers.src/lib/output.ts:323: The plain-
--jsoncontract change (addingpostedUid/uidsToNotifytoCOMMENT_ESSENTIAL_FIELDS) isn't pinned by any test. The existingcomment view --jsonandcomment add --jsontests only assertid/content/postedAt, so dropping these two fields from the essential list would pass CI. Assert that both keys appear in non---fullJSON output.src/lib/comment-recipients.test.ts:43: This fixture puts the newest comment first, so the test doesn't actually exercise the 'not the first page' behavior its name claims — it would still pass if
getLatestCommentreturnedresults[0]. The within-page newest-not-first case is already covered by the integration testdefaults a reply...in comment.test.ts (older comment listed first). Either reorder the fixture so the stale comment comes first, or drop this test as redundant.
Three issues from review. --notify me (or an id: ref) on a task in a personal project threw NOT_SHARED. Neither needs to know who shares the project, so both now resolve before that check, and NOT_SHARED is raised only for the name or email refs that genuinely need the list -- naming which ones they were. resolveNotifyIds duplicated the per-ref matching in resolveAssigneeId, down to the ambiguity error. Both now call one matchCollaboratorRef helper so the two cannot drift apart. The Notified line in `comment view` had no coverage at all. Tested now: rendered names, the task and project routes to the collaborator list, the raw-ID fallback when the lookup fails, and that an empty recipient list neither prints nor looks anything up.
Contributor
|
🎉 This PR is included in version 3.2.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
td comment addto parity with the MCP, which shipped this in Doist/todoist-mcp#580 for Doist/todoist-mcp#509.Stacked on #477 — based on that branch for SDK 14.0.1, which carries the
uidsToNotifyserialisation fix. Retarget tomainonce #477 merges.The problem
Comments posted with
tdnotify nobody.comment addnever sentuidsToNotify, so a teammate named in a comment found out only if they happened to open the task.That also breaks the next comment, which is the part people notice and can't reproduce. Todoist's clients pick the recipients themselves and send them with each comment; the API notifies exactly who it is handed and derives nobody on its own. On a first comment the clients notify the assignee, the assigner and the creator; on a reply they notify the previous comment's participants, so threads keep flowing without everyone being re-tagged. A comment posted with an empty list therefore silences the comment that follows it — including one a human later writes in the app.
What this does
comment addgains--notify, accepting names, emails,id:xxxor"me", comma-separated in the same style as--labels.--no-notifyposts in silence, matching the existing--no-labelsnegation.Omitting
--notifymirrors the clients: assignee, assigner and creator on a task's first comment, or the previous comment's participants on a reply, always excluding the author. The rules live insrc/lib/comment-recipients.ts, ported from the MCP's equivalent.Two things worth calling out in review:
--notify mesilently did nothing.paginate(), which accumulates every result. Comments come back oldest-first with no reverse option, so reaching the last page is unavoidable; holding the whole history is not. Review flagged exactly this on the MCP version.resolveNotifyIdsis pure over an already-fetched collaborator list, so the--notifypath fetches collaborators once and reuses them to render the names back — my own tests caught a double fetch here.@mentionsin the comment text are deliberately not parsed; the user names people with--notify.SKILL_CONTENTdocuments this so agents don't assume the text alone notifies.Notification on edit is out of scope —
UpdateCommentArgsis{ content }only.Surfacing
Who was notified is now visible in three places: the confirmation line after adding, a
Notified:line incomment view(resolved only when there are recipients, so the common case costs no extra request), andpostedUid/uidsToNotifyin plain--jsonrather than only under--full.Verification
npm run type-check,npm run check,npm run build,npm run check:skill-syncand all 1815 tests pass (suite run three times to rule out flakiness).Live against the real API on a throwaway task in a shared project, since deleted:
--dry-runwith--notify--notify "Ada Lovelace"Notified: Ada L.--notify "me,ada@example.com"--notify Ghost,PhantomASSIGNEE_NOT_FOUNDnaming both--no-notifycomment view/--jsonThe paths involving a second person — a reply inheriting someone else's participants, and a first comment on a task assigned to someone else — are covered by unit tests rather than live calls, since verifying them for real means sending test notifications to an actual colleague.
🤖 Generated with Claude Code