Repository navigation
Description
♻️ Reproduction Steps
Summary
When Anthropic models produce reasoning/thinking tokens and CopilotKit emits REASONING_* events in the AG-UI SSE stream, the agent stalls permanently after the thinking phase completes. No further tool calls, text generation, or RUN_END events are emitted. The agent simply stops.
Stripping all REASONING_* events from the SSE response stream before they reach the frontend resolves the issue — the agent proceeds normally with tool calls and text output.
Detailed description
Setup
We use BuiltInAgent from @copilotkitnext/agent with Anthropic models. Thinking/reasoning is configured in two complementary ways:
-
Vercel AI SDK
providerOptions— passed tocreateLanguageModelForAgent:providerOptions: { anthropic: { thinking: { type: 'adaptive' }, effort: 'medium', } }
-
HTTP-level injection — a custom
fetchwrapper that intercepts the outbound Anthropic API request and injectsthinking,max_tokens, and model-level effort params into the JSON body (as a fallback / override for values the SDK may not pass through).
Both approaches work correctly at the Anthropic API level — thinking tokens are generated and streamed back.
What we tried (chronologically)
-
Verified
@ai-sdk/anthropicversion: Initially had2.0.56which lackedtype: 'adaptive'support in its Zod schema. Upgraded to2.0.67which natively supports adaptive thinking. Did not fix the stall. -
Verified CopilotKit version consistency: Ensured all
@copilotkit/*and@copilotkitnext/*packages are at1.52.1(the version that added reasoning support per PR #3165). Did not fix the stall. -
Increased
maxTokens: SetmaxTokensto the model maximum (64000 for Sonnet 4.6) to rule out token budget exhaustion. Did not fix the stall. -
Disabled SDK-level thinking settings (kept HTTP-level injection): Set
providerOptions.anthropic.thinkingtoundefinedso the SDK is unaware of thinking, but the HTTP-level fetch wrapper still injectsthinking: { type: 'adaptive' }into the raw API request body. Anthropic still returns thinking tokens, and the agent still stalled. This proved the issue is not in how we configure the SDK, but in how CopilotKit handles the streamed reasoning events. -
Stripped all
REASONING_*events from the SSE stream: Added aTransformStreamin our response pipeline that filters out any SSEdata:line where the parsed JSON hastypestarting with"REASONING". This fixed the stall. Agents proceed normally to tool calls and text generation.
Workaround (current)
We strip REASONING_* events from the AG-UI SSE stream before the response reaches the frontend:
// Inside our SSE TransformStream rewrite function
const parsed = JSON.parse(jsonPart)
if (parsed?.type?.startsWith?.('REASONING')) return '' // drop the lineThis is behind a feature flag so we can re-enable reasoning events when a fix is available.
Key observation
The Anthropic API call itself succeeds. Thinking tokens are generated and the model produces a complete response (tool calls and/or text). The issue is entirely in how CopilotKit's runtime translates the Vercel AI SDK's reasoning stream into AG-UI SSE events. It appears that REASONING_END is never emitted, which may leave the runtime's internal state machine stuck in a "reasoning" state, preventing it from emitting subsequent text or tool call events. When REASONING_* events are stripped from the stream, the agent works perfectly — even though Anthropic is still performing reasoning internally (the thinking just isn't surfaced to the frontend).
Expected behavior
After reasoning content finishes streaming, a REASONING_END event should be emitted, followed by the agent continuing its run with TEXT_MESSAGE_START, TOOL_CALL_START, or RUN_END as appropriate. The presence of REASONING_* events in the stream should not cause the agent run to stall.
Steps to reproduce
- Configure a
BuiltInAgentwith an Anthropic model that supports thinking (e.g., Claude Sonnet 4.6). - Set
providerOptions.anthropic.thinkingto{ type: 'adaptive' }(or{ type: 'enabled', budgetTokens: N }). - Send a message that triggers agent tool use (not just a simple text reply).
- Observe that
REASONING_STARTandREASONING_MESSAGE_CONTENTevents are emitted, but noREASONING_ENDever arrives. The stream stalls — no further events of any kind. - Strip all
REASONING_*events from the SSE stream and repeat — agent proceeds normally.
Possibly related
- CopilotKit PR #3165 — Added reasoning message support
- The issue may be in how the runtime converts Vercel AI SDK reasoning chunks into AG-UI events — specifically,
REASONING_ENDnever being emitted, which could block the state machine from transitioning to text/tool-call phases
✅ Expected Behavior
SSE returns REASONING_END which then resumes flow.
❌ Actual Behavior
What happens
- Agent receives a user message and starts a run.
- Anthropic returns thinking/reasoning content. CopilotKit's runtime translates these into AG-UI SSE events:
REASONING_STARTREASONING_MESSAGE_CONTENT(multiple, streaming the thinking text)
- After the reasoning content finishes streaming, the stream stalls. No
REASONING_ENDis ever emitted. NoTEXT_MESSAGE_START, noTOOL_CALL_START, noRUN_END— nothing. The stream hangs indefinitely. - The frontend shows the thinking content but the agent never proceeds to action.
Screenshots
No response
𝌚 CopilotKit Version
## Environment
| Dependency | Version |
|---|---|
| `@copilotkit/runtime` | `1.52.1` |
| `@copilotkitnext/agent` | `1.52.1` |
| `@copilotkit/react-core` | `1.52.1` |
| `@ai-sdk/anthropic` | `2.0.67` |
| `ai` (Vercel AI SDK) | `5.0.116` |
| Node.js | v24 |
**LLM models used:** Anthropic Claude Sonnet 4.6, Claude Opus 4.6, Claude Opus 4.5 — all with `thinking` configured via the Vercel AI SDK's `providerOptions`.
Activity
linussuperfly commentedon Mar 16, 2026
Here is actual root cause and solution for this bug @giulio-leone
CopilotKit Bug Report: Agent stalls when Anthropic reasoning/thinking is enabled
Environment
@copilotkit/runtime1.52.1@copilotkitnext/agent1.52.1@copilotkit/react-core1.52.1@ag-ui/core0.0.46@ag-ui/client0.0.46@ai-sdk/anthropic2.0.67/0.78.0ai(Vercel AI SDK)5.0.116Models tested: Anthropic Claude Sonnet 4.6, Claude Opus 4.6, Claude Opus 4.5 -- all with
thinkingconfigured viaproviderOptions.anthropic.thinking.CopilotKit mode:
useSingleEndpoint={true}, V2 interface (@copilotkit/react-core/v2).Symptom
When Anthropic models produce reasoning/thinking tokens, the agent stalls permanently after the reasoning phase completes. The UI shows reasoning content streaming in, but once the last
REASONING_MESSAGE_CONTENTevent is processed, no further events are delivered to the application -- noREASONING_MESSAGE_END, noTEXT_MESSAGE_START, no tool calls, noRUN_FINISHED. The agent appears frozen.Disabling reasoning (so the model does not produce thinking tokens) makes the agent work perfectly. Re-enabling reasoning reproduces the stall 100% of the time.
Root cause
There are two issues in
@copilotkitnext/agent'sBuiltInAgentreasoning event handling:Issue 1: Empty reasoning deltas kill the event pipeline (critical)
The
reasoning-deltacase in thefullStreamloop createsREASONING_MESSAGE_CONTENTevents with:When the Vercel AI SDK produces a
reasoning-deltawhere bothpart.textandpart.deltaareundefinedornull, the??operator falls back to""(empty string).The
@ag-ui/coreZod schema forReasoningMessageContentEventSchemahas a refinement that rejects empty deltas:This causes
EventSchemas.parse()to throw inside@ag-ui/client'stransformHttpEventStreamfunction (L). The thrown error is caught by the generic handler:The
t.error(e)call errors the outputSubject, which propagates through the RxJS Observable pipeline. InuseSingleEndpointmode, both therunAgentandconnectAgentpipelines process events from the same backend SSE stream -- both die simultaneously when the validation error propagates.After the pipelines die, the backend continues streaming remaining events (
REASONING_MESSAGE_END,REASONING_END,TEXT_MESSAGE_START, tool calls,RUN_FINISHED), but they are never processed by the dead frontend pipelines.Critically, no
onRunFailedcallback fires because thecatchErroroperator in the pipeline returns a completing Observable (viaonError->stopPropagation), so the error is silently swallowed from the application's perspective. The only visible symptom is the agent appearing frozen.Issue 2: No defensive auto-close for reasoning state (secondary)
If the AI SDK does not produce a
reasoning-endevent (e.g., due to an abort, error, or the model transitioning directly to text/tool output), theBuiltInAgentnever emitsREASONING_MESSAGE_ENDorREASONING_END. This leaves the AG-UI event state machine in an inconsistent state.This is the issue addressed by PR #3399, but that PR alone does not fix the stall because the empty-delta issue (Issue 1) kills the pipeline before any auto-close logic can run.
Fix
Two changes in
@copilotkitnext/agent'sBuiltInAgentfullStreamloop:1. Skip empty reasoning deltas
2. Defensive auto-close for reasoning state
Track whether the agent is currently in a reasoning phase and auto-close it when transitioning to a different state:
Set
isInReasoning = truein thereasoning-startcase,isInReasoning = falsein thereasoning-endcase, and callcloseReasoningIfOpen()at the start of:aborttool-input-starttext-starttool-callfinisherrorcatchblock of thefor awaitloopHow we diagnosed this
Multi-layer SSE logging confirmed that the backend sends all events correctly (including
REASONING_MESSAGE_END,REASONING_END,TEXT_MESSAGE_START,RUN_FINISHED). A rawwindow.fetchinterceptor on the frontend confirmed the browser receives all these events over the SSE stream.Agent event subscription (
agent.subscribe({ onEvent, onRunFailed, onRunFinalized })) showed that the AG-UIapply()pipeline processesREASONING_MESSAGE_CONTENTevents correctly, thenonRunFinalizedfires prematurely.onRunFailednever fires.Pipeline-level diagnostics in
@ag-ui/client(labelingrunAgentandconnectAgentfinalizecallbacks withconsole.trace()) revealed that therunAgentpipeline finalizes viaSubject2.errororiginating fromEventSchemas.parse()intransformHttpEventStream. The stack trace showed the error propagating throughcatchErrorwhich swallows it and completes the pipeline.Inspecting
@ag-ui/coreschemas revealed theReasoningMessageContentEventSchemahasdelta: z.string().refine(s => s.length > 0), which rejects empty strings.Inspecting
@copilotkitnext/agentsource confirmed thereasoning-deltahandler uses?? ""as a fallback, producing empty deltas that fail validation.Related
fix(agent): skip empty reasoning deltas and auto-close reasoning life…
fix(agent): skip empty reasoning deltas and auto-close reasoning life…
fix(agent): skip empty reasoning deltas and auto-close reasoning life…
fix(agent): skip empty reasoning deltas and auto-close reasoning life…
fix(agent): skip empty reasoning deltas and auto-close reasoning life…