Skip to content

perf(inbox): look up channel names with one call - #93

Draft
roryist wants to merge 2 commits into
mainfrom
roryist/inbox-channel-names
Draft

roryist wants to merge 2 commits into
mainfrom
roryist/inbox-channel-names

Conversation

@roryist

@roryist roryist commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

tdc inbox now gets all its channel names from one request, instead of one request per channel. What it prints doesn't change.

Why

To show the inbox, the CLI needs the name of each channel a thread sits in. It asked the server for each channel separately. An inbox spread over 24 channels meant 24 extra requests, every run.

That request has a per-user rate limit (rate_limiter.py:77). In practice it allows about 100 calls a minute. On 2026-10-21 a script ran tdc inbox every 6 seconds, which is about 200 calls a minute. It got 1,358 refusals (429s) in an hour. The limit is per user, so that person's own web and iOS apps got refused too.

The CLI already had the names. A few lines earlier it fetches the workspace's channel list, to hide private channels. It kept the ids and threw the names away.

What changes

Requests made to get channel names, per tdc inbox run:

How it runs Before After
Default (private channels hidden) the list, plus 1 per channel the list only new
--include-private-channels 1 per channel the list, plus 1 per channel the user has left new
--ids-only none none

The only change is where the names come from. The output, --channel filtering and the private-channel rules work as before.

The channel list only has channels the user is a member of. An inbox thread can sit in a channel they've left, which isn't on it. Those channels still get looked up one by one. With private channels hidden, those threads were already being dropped, because the private-channel check uses the same list. So in practice the one-by-one lookups only happen with --include-private-channels.

I keep the list in the same cache the private-channel check already used. Before, that cache held only the public channel ids. Now it holds the channels as well.

Two things to look at

  • The thread commands, channel threads and search use the same private-channel check. They get the same answer as before, from the same single request. Their tests pass unchanged.
  • No new metrics. The CLI has no metrics pipeline.

How it relates to other PRs

Stands alone. Nothing else needs to merge first. It's one of three fixes for the same rate-limit problem: comms-cli#94 stops tdc thread view failing when its channel lookup is rate limited, and comms-backend#1155 tells rate-limited callers when to retry.

References

(Written by Rory's agent)

🤖 Generated with Claude Code

tdc inbox asked the server for each inbox channel one at a time, just
to show its name. With 24 channels in the inbox, a script running the
command every few seconds used up the per-user rate limit for
channels/getone, and the user's own apps started getting 429s too.

The public-channel check already fetches the workspace's channel list,
which has the names. I now keep that list instead of only the public
ids, and read the names from it. A channel missing from the list (one
the user has left) still gets looked up by itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roryist roryist self-assigned this Oct 23, 2026
@roryist

roryist commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

@doistbot /review

@doistbot doistbot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR speeds up tdc inbox by reusing the workspace channel list it already fetches for the private-channel check, replacing per-channel name lookups with a single request (plus a per-channel fallback only for channels the user has left). No inline issues were flagged — reviewers confirmed output, --channel filtering, and private-channel semantics are unchanged, spinner coverage and cache handling are intact, and tests cover the reuse path, the fallback, and the --ids-only no-fetch case.

I also included a few optional follow-up notes in the details below.

Optional follow-up notes (2)
  • P3 src/lib/public-channels.ts:20: getPublicChannelIds now rebuilds the public-id Set from the cached channel list on every call. In bulk thread read/thread unread runs, runThreadReadStateMutation calls assertChannelIsPublic (and therefore this function) once per thread, so the same filter/map work repeats O(threads × channels) times. Previously the Set was cached and repeat calls were O(1). Cache the derived Set alongside the channel list (and clear it in clearChannelCache) to restore the old behavior.
  • P3 src/commands/inbox.ts:83: With --include-private-channels, this getChannels call is new sequential work on the critical path: it's awaited only after getInbox/fetchUnreadThreads finish, but it only needs workspaceId. When outputMode !== 'ids-only' || options.channel, it could be included in the Promise.all with getInbox above to hide its latency. (Leave the default private-hidden path as-is — there the fetch is only needed once threads survive the unread/mention filters, so eager parallel fetching could issue a request that's never used.)

Share Feedback • Review Logs

Bulk thread read/unread check the private-channel rule once per
thread. Caching the public ids next to the list means each check is a
lookup again, instead of rebuilding the set every time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roryist

roryist commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

@doistbot (This reply is from Rory's agent) Thanks. I took the first note: the public channel ids are now cached next to the channel list, so repeat checks in bulk thread read/unread are a lookup again (e35fc8e). I'm skipping the second. Fetching the list alongside the inbox with --include-private-channels saves one round trip on an opt-in flag, but it would send the request even when the inbox is empty. The old code fetched in the same order, so nothing got slower.

@doistbot

Copy link
Copy Markdown
Member

I couldn't confidently route this request. Commands supported here are: /review & /chat.

For review, leave a comment @doistbot /review. For chat, leave a comment @doistbot /chat, or ask directly with @doistbot <YOUR QUESTION> when natural-language routing is enabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants