Repository navigation
[Bug]: MCP thread tools replace the orchestrator's rejection reason with "The operation could not be completed." #15586
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks so much for the precise report and the code pointers, @angelospk. I confirmed this on current
main(eac52f00), and it's separate from #15566 and #13630.What I found
Refusing to settle a thread that still has work is correct. The problem is that the sentence the agent needs gets thrown away before the tool result is built.
t3_thread_organizedispatchesthread.settleand maps every failure throughunavailable()(apps/server/src/mcp/toolkits/thread/handlers.ts:300). The shareddispatchhelper does the same (:48), so queue edit, cancel, reorder, and promote lose their reasons too.t3_pending_request_respond(:219) follows the same pattern.unavailable()(apps/server/src/mcp/threadAccess.ts:15-19) always returnsorchestration_error/The operation could not be completed.and ignores the error it's given.- The settle refusal is an
OrchestratorDispatchErrorwhosecauseis the stringThread … has active or blocked work and cannot be settled.(apps/server/src/orchestration-v2/Orchestrator.ts:2511-2515). Its ownmessageis only theFailed to dispatch orchestration command thread.settle (…)wrapper (:130-132). That matches your trace. - Other decider refusals have the same shape, for example snooze (
has a pending approval…,has a queued run…) and queue cancel (Run … is not queued.).
The generic text is deliberate for unexpected failures.
apps/server/src/mcp/toolkits/core.test.ts(returns a bounded public failure without serializing storage causes) checks that a projection error with a private storage path as its cause still comes back as the generic message.ThreadMetadataMcpService.threadLookupFailurepasseserror.messagethrough, but for this error that would only be the dispatch wrapper, not the cause. Forwarding everycauseblindly could also leak internals, since transcript hydration wraps arbitrary failures inOrchestratorDispatchError(ensureCommandTranscriptsinThreadManagementService.ts).Likely fix area
- One option is that, on the dispatch paths above, when the error is an
OrchestratorDispatchErrorwith a stringcause, that string becomes theOrchestratorMcpFailuremessage. unavailable()could stay as-is for lookup, projection, and non-string causes, so storage paths and internal defects remain redacted.- The string could ride on the existing
orchestration_errorcode. A new code likecommand_rejectedwould be a contract change toOrchestratorMcpFailure, so that part is optional.
A maintainer will decide on the fix direction.
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 4, 2026 I am also having the same issue. Although I understand that this is, in a sense, expected behavior since the thread is still running, it would be useful in many cases to have a thread that can settle itself when done. Perhaps having that as an explicit, separate option for the thread settle tool would make sense.
Thank you for all your work on T3 code!
Before submitting
Area
apps/server
Steps to reproduce
t3_thread_organizewithaction: "settle"and nothreadId.Expected behavior
The tool fails with the reason the orchestrator gave ("Thread … has active or blocked work and cannot be settled."), so the agent can tell the user why and what to do instead.
Actual behavior
The tool returns
{"_tag":"OrchestratorMcpFailure","code":"orchestration_error","message":"The operation could not be completed."}. The agent can't tell why, retries, and then tells the user that settle failed "because of a T3 error". The real reason only shows up inserver.trace.ndjson.t3_thread_organize(apps/server/src/mcp/toolkits/thread/handlers.ts:300) and the shareddispatchhelper (:48) both dothreads.dispatch(command).pipe(Effect.mapError(unavailable)).unavailable()(apps/server/src/mcp/threadAccess.ts:15) always returns the same generic message, so the decider'sOrchestratorDispatchErrorcause is dropped. Any decider rejection for a command dispatched through those handlers (organize, queue cancel/reorder/promote and so on) loses its reason the same way, not just settle.ThreadMetadataMcpService.tsalready passeserror.messagethrough (threadLookupFailure,:38-43), so surfacing it has precedent.A possible fix: map
OrchestratorDispatchErrorto a failure that carries itscause, maybe under its own code such ascommand_rejected, and keep the generic message for unexpected internal errors. If the generic message is there on purpose to avoid leaking internals, a small set of user-facing rejection reasons would still help agents.Impact
Minor bug or occasional failure
Version or commit
0.0.46-nightly.20261004.2644. The same code is on
mainateac52f00.Environment
Linux x86-64, desktop app, Claude provider.
Logs or stack traces
Workaround
Settle the thread by hand from the sidebar after the turn finishes.
Related: #13630 (settle a running thread once it goes idle), #15566 (typed MCP failures returned with
isError: false).