Repository navigation
[Bug]: An agent that receives a t3_thread_send message can't tell which thread sent it (senderThreadId is recorded but t3_thread_read omits it) #17517
Description
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
- Open a Claude thread (parent) and have it call
delegate_tasktwice, so it has two child threads. I used Codexgpt-6.1-sol(reasoninglow) and Codexgpt-6-luna. - Open one child thread and ask it to send a message to its parent with
t3_thread_send. - In the parent, ask the agent which child sent that message, using only what it received and what
t3_thread_readreturns for its own thread.
Expected behavior
The receiving agent can identify the sender. The server already records senderThreadId on the message and the web and mobile timelines use it, so the same value should be available to the agent: as a senderThreadId field on the item returned by t3_thread_read, and ideally as a server-authored line in the delivered turn so the agent does not need a second call.
Actual behavior
The message arrives in the parent as a plain user turn. Nothing in it names the sending thread or its model.
t3_thread_read on the parent returns this for the item (IDs shortened):
type: "user_message"
createdBy: "agent"
creationSource: "mcp"
sourceThreadId: "<the parent's own thread id>"
messageId: "message:mcp:<uuid>:thread-send:<clientRequestId>"
createdBy and creationSource show that an agent sent it, but not which one. sourceThreadId is the receiving thread. The messageId carries the sender's clientRequestId and no thread id. t3_thread_transfers lists only the delegate_task spawn and result transfers, so the message is not there either.
The parent could only identify the sender indirectly: it read the timeline of each child thread and found the t3_thread_send call whose returned messageId matched. That works only for threads the receiver already knows about and can read.
Once the sender is known, its model and effort are easy to get (t3_thread_list and t3_thread_configuration returned gpt-6.1-sol and low correctly). The missing piece is the sender's thread id.
In the code at main @ ec80933:
sendToThreadpassessenderThreadIdtoThreadManagementService:
...(parent === undefined ? {} : { senderThreadId: parent.thread.id }), timelineItem, which builds thet3_thread_readitems, returnscreatedByandcreationSourcebut notsenderThreadId:
t3code/apps/server/src/mcp/OrchestratorMcpService.ts
Lines 792 to 828 in ec80933
function timelineItem(input: { readonly row: OrchestrationV2ThreadProjection["visibleTurnItems"][number]; readonly maxChars: number; readonly textOffset?: number; readonly messagesByThreadId: ReadonlyMap<ThreadId, OrchestrationV2ThreadProjection["messages"]>; }): OrchestratorMcpThreadTimelineItem { const text = turnItemText(input.row.item); const offset = input.textOffset ?? 0; const end = offset + input.maxChars; const textTruncated = text !== null && text.length > end; const messageId = input.row.item.type === "user_message" || input.row.item.type === "assistant_message" ? input.row.item.messageId : null; const message = messageId === null ? undefined : input.messagesByThreadId .get(input.row.sourceThreadId) ?.find((candidate) => candidate.id === messageId); return { position: input.row.position, visibility: input.row.visibility, sourceThreadId: input.row.sourceThreadId, itemId: input.row.sourceItemId, runId: input.row.item.runId, messageId, createdBy: message?.createdBy ?? null, creationSource: message?.creationSource ?? null, type: input.row.item.type, status: input.row.item.status, title: input.row.item.title, text: text === null ? null : text.slice(offset, end), textTruncated, nextTextOffset: textTruncated ? end : null, updatedAt: DateTime.formatIso(input.row.item.updatedAt), };
The line references are from main, not from the exact nightly build I ran; the behavior itself was observed live.
Related: #6573 proposed server-authored sender attribution for inter-thread messages and was closed unmerged during backlog cleanup. #15250 is a different problem (a Codex child's model reported as "Not reported").
Impact
Minor bug or occasional failure
Version or commit
T3 Code (Nightly) 0.0.46-nightly.20261009.2873
Environment
macOS 15.7.7, Apple Silicon, desktop app. Parent: Claude Code 2.1.295, claude-opus-5-5. Children: Codex CLI 0.162.0, gpt-6.1-sol (reasoning low) and gpt-6-luna.
Logs or stack traces
Sender side (child thread, gpt-6.1-sol), t3_thread_send result:
{
"threadId": "<parent-thread-id>",
"messageId": "message:mcp:<uuid>:thread-send:subagent-connectivity-proof-9283-20261009",
"runId": "run:thread:<parent-thread-id>:ordinal:2",
"status": "starting",
"delivery": "started"
}
Receiver side (parent thread), the same message in t3_thread_read:
{
"sourceThreadId": "<parent-thread-id>",
"messageId": "message:mcp:<uuid>:thread-send:subagent-connectivity-proof-9283-20261009",
"createdBy": "agent",
"creationSource": "mcp",
"type": "user_message"
}Screenshots, recordings, or supporting files
No response
Workaround
Have the sender put its own threadId in the message text, or have the receiver read each candidate thread with t3_thread_read and match the messageId returned by the sender's t3_thread_send call. The first relies on the sender being truthful, the second only works for threads the receiver can already read.
Activity