ag-ui-protocol / ag-ui-protocol/ag-ui
[Bug]: @ag-ui/mastra: bare JSON.parse on replayed tool-call arguments makes a thread permanently unrunnable after concurrent runs corrupt in-memory state
- Linguagem predominante
- Python
- Estrelas
- 15.9k
- Forks
- 1.4k
- Merge médio
- 1d 17h
- PRs com merge (30d)
- 163
Descrição
### Pre-flight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest are #2380, which concatenates *text* segments and explicitly does not affect tool-call arguments, and #2416, which is about the missing `RUN_ERROR`).
- [x] Verified against the latest `@ag-ui/mastra` (1.1.2): the code path is unchanged.
### Describe the Bug
`convertAGUIMessagesToMastra` (`integrations/mastra/src/utils.ts`) parses tool-call arguments with a bare `JSON.parse`:
```ts
for (const toolCall of message.toolCalls ?? []) {
parts.push({
type: "tool-call",
toolCallId: toolCall.id,
toolName: toolCall.function.name,
args: JSON.parse(toolCall.function.arguments), // utils.ts:171 in 1.1.2
});
}
```
If a single assistant message in the history carries a malformed `arguments` string, the converter throws, the run dies before reaching the model, and — because the same history is replayed on every subsequent run — **the thread never recovers**. Every later `agent/run` on that thread fails the same way. The only way out is a restart of the server process (see below for why).
The malformed value we observe is two JSON objects concatenated with no separator, e.g. `{"mine":true}{"mine":true}`. The error surfaces as:
```
SyntaxError: Unexpected non-whitespace character after JSON at position 245 (line 1 column 246)
```
The run fails in ~80 ms (never reaches the model provider).
**How it gets into that state.** We have not isolated a minimal repro yet, but the conditions we reliably observe are: several concurrent `agent/connect`/`agent/run` requests for the **same thread** (a page mounted with multiple subscribers fired 8 requests within 4 ms), combined with the client switching agent/thread while a run was still in flight. Postgres (`@mastra/pg` memory) stays clean — `mastra_messages` holds a single, well-formed tool call. The corrupted `arguments` live only in the process's in-memory conversation state, so a process restart "fixes" the thread. This suggests the duplicate tool-call chunks for one `toolCallId` are being appended rather than deduplicated somewhere between the runtime and the converter.
Two separable problems:
1. **Robustness (this issue):** a single malformed history entry should not permanently kill a thread. `JSON.parse` on replayed history should be guarded — drop/skip the bad tool-call part (and ideally surface a warning) instead of throwing out of the converter.
2. **Root cause:** concurrent runs on one thread can leave duplicated/concatenated tool-call `arguments` in in-memory state. Happy to open a separate issue with a repro once we have one isolated; filing the robustness part now because it is independently true and independently fixable.
### Steps to Reproduce
Robustness half (deterministic):
1. Build an AG-UI run input whose history contains an assistant message with `toolCalls: [{ id: "x", type: "function", function: { name: "anyTool", arguments: '{"a":1}{"a":1}' } }]`.
2. Pass it to a local `MastraAgent` (`getLocalAgents` / `MastraAgent.run`).
3. The run rejects with the `SyntaxError` above before any model call; repeat with the same history and it fails identically every time.
Corruption half (not yet minimal): fire several concurrent `agent/connect` + `agent/run` requests for the same `threadId`, switch the client to another agent/thread while a run is in flight, then come back and run on the original thread.
### Expected Behavior
A malformed tool-call `arguments` string in replayed history degrades (that tool-call part is skipped, a warning is logged) rather than throwing from the converter and making the thread permanently unrunnable. Duplicate tool-call chunks for the same `toolCallId` are deduplicated, not concatenated.
### Environment
- `@ag-ui/mastra` 1.1.1 (verified unchanged in 1.1.2)
- `@mastra/core` 1.57.0, `@mastra/memory` 1.26.0, `@mastra/pg` 1.19.0
- `@copilotkit/runtime` 1.67.1 (`copilotRuntimeNodeHttpEndpoint`, local agents via `getLocalAgents`)
- Node 22, Linux; model provider Vertex (irrelevant — the failure happens before the model is called)
### Logs & Errors
```
SyntaxError: Unexpected non-whitespace character after JSON at position 245 (line 1 column 246)
```
Server-side request log for the failing run shows the run completing in ~80 ms with no model span. Memory storage for the same thread shows a single well-formed tool call with `arguments: {"mine":true}`.
### Additional Context
Related but distinct: #2416 (no `RUN_ERROR` emitted on failure) makes this worse from the client's side — the stream just ends, and a client that reconnects on an incomplete stream ends up retrying the same doomed run in a loop.
We are adding a guard on our side (sanitizing tool-call parts before the runtime sees the history) and will report back with a minimal repro for the concurrency half if we manage to isolate it.
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.