[Bug]: t3_thread_read results overflow Claude Code's MCP output limit on long threads#33417
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Use a thread with a few thousand timeline items. Mine has 3,589.
- From Claude Code, call
t3_thread_readon it with no options, which meansview: messages,limit: 50,maxCharsPerItem: 20000. - Call it again with
{ view: "activity", limit: 100, maxCharsPerItem: 1500 }. That's the call agents make most. - Call it again with
{ view: "activity", limit: 100, maxCharsPerItem: 1 }.
Response sizes from a 0.1.8-nightly.20261104.4192 server:
| call | chars |
|---|---|
| defaults (messages, 50 × 20000) | 69,714 |
defaults, view: activity | 121,806 |
| activity, 100 × 1500 | 136,430 |
| activity, 100 × 1 | 60,271 |
Claude Code rejects MCP results over MAX_MCP_OUTPUT_TOKENS, which defaults to 25k tokens. For this JSON that works out to about 50k chars. In my transcripts the largest t3_thread_read result that got through was 48,608 chars and the smallest one rejected was 54,205.
Expected behavior
A read with default or documented options fits in the agent's tool output limit. An agent that wants the latest messages gets them in one call.
Actual behavior
There are two causes.
- Pages always start at the oldest item.
ProjectionStore.getTimelinePagetakesmatching.slice(0, options.limit)afterafterPosition. With no cursor, a read returns the beginning of the thread. To reach the newest message an agent has to walk forward withafterPosition=nextPosition, which is 36 pages of 100 on this thread. T3's own CLI works around this.readLatestMessageswidens a backward window, doubling it each pass, to find the last few messages. - Nothing bounds the total size.
limit×maxCharsPerItemonly bounds item text, and the defaults allow 50 × 20,000. Every item also carries 500 to 650 chars of metadata.itemIdalone is about 143 chars and contains the thread id twice, andsourceThreadIdandrunIdrepeat it again. Solimit: 100, maxCharsPerItem: 1still returns 60k chars. LoweringmaxCharsPerItemcan't make a 100-item page fit.
What agents actually hit, from 538 Claude Code sessions on my machine:
- 308
t3_thread_readcalls. 137 of them (44%) were rejected for size, in 16 sessions. - 133 of those 137 already passed
limit: 100and a smallmaxCharsPerItem(usually 1500), as the tool description asks ("Keep reads small"). - One session read a single thread 48 times, walking
afterPosition99, 199, 299 … 4099 to reach the end. 41 of those reads were rejected.
When a result is rejected, Claude Code saves it to a file. The agent then reads that file back in slices or writes a script to filter it. That costs extra turns and context, and T3 never sees any of it.
Impact
Major degradation or frequent failure
Version or commit
0.1.8-nightly.20261104.4192. The same code is on main @ b8c2cfad6b.
Environment
Linux 6.17, Claude Code 2.1.319 (default MAX_MCP_OUTPUT_TOKENS), Node 26.10. Threads run on Codex and Claude providers.
Logs or stack traces
| 1 | Error: result (136,430 characters) exceeds maximum allowed tokens. Output has been saved to <path> |
Workaround
Agents call t3_thread_read with limit ≤ 20, or walk pages inside a script and keep only the tail. Neither helps an agent that doesn't know the thread is long.
I'd like to send the PR for this, and I have a fix in mind:
- Response budget in
readThread. Stop adding items once the encoded result reaches about 30k chars, and reporthasMoreas today. Always return at least one item, cut with the existingtextTruncated/nextTextOffset. A read then fits whatever options the agent passes. - Newest page by default. With no cursor, return the newest
limititems, still in chronological order inside the page. AddbeforePositionto go back, and returnearlierPositionwith the result.afterPositionkeeps working as now for "what's new since my last read". The CLI'sreadLatestMessagesbecomes a single call. - Lower defaults (
limit20,maxCharsPerItem4000,runLimit3), and a tool description that says how to page in each direction.
I simulated this against the same live threads. The newest 20 messages come to 16k to 20k chars. The newest 20 activity items come to 14k to 28k with the budget, against 46k without it on one thread.
Point 2 changes a default, so I'd like a yes or no on it before I build it. If you'd rather keep the oldest-first default, I'll send 1 and 3 alone.