fix(agent): drop rawOutput for non-MCP tool results#2626
Conversation
Standard tool results (Read, Bash, ...) render from `content`, so also copying the full result into `rawOutput` duplicated large payloads on their way to the renderer. Only MCP tools consume rawOutput (App bridge replay), so emit it solely when an MCP tool result is present.
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
packages/agent/src/adapters/claude/conversion/sdk-to-acp.test.ts:310-341
**Prefer parameterised tests here**
The two `it(...)` blocks share identical scaffolding — `registerToolUse`, clearing `updates`, calling `handleUserAssistantMessage`, and calling `toolUpdate` — differing only in the tool name, the presence of `tool_use_result`, and the expected `rawOutput` shape. The team's coding standard asks for parameterised tests wherever the same logical flow is exercised with varied inputs. An `it.each` table that passes `toolName`, `toolUseResult`, and `expectedRawOutput` (nullable) would express this once, making future cases (e.g. a third tool type) a single table row rather than a copied block.
Reviews (1): Last reviewed commit: "fix(agent): drop rawOutput for non-MCP t..." | Re-trigger Greptile |
| await registerToolUse(context, "tool_read", "Read", { | ||
| file_path: "/test/a.ts", | ||
| }); | ||
| updates.length = 0; | ||
| await handleUserAssistantMessage( | ||
| userToolResult("tool_read", "line one\nline two"), | ||
| context, | ||
| ); | ||
| const update = toolUpdate(updates); | ||
| expect(update?.status).toBe("completed"); | ||
| expect(update && "rawOutput" in update).toBe(false); | ||
| }); | ||
|
|
||
| it("forwards rawOutput for MCP tool results consumed by the App bridge", async () => { | ||
| const { context, updates } = createHandlerContext(); | ||
| await registerToolUse(context, "tool_mcp", "mcp__srv__do"); | ||
| updates.length = 0; | ||
| await handleUserAssistantMessage( | ||
| userToolResult("tool_mcp", "ignored", { | ||
| content: [{ type: "text", text: "mcp payload" }], | ||
| structuredContent: { ok: true }, | ||
| }), | ||
| context, | ||
| ); | ||
| const update = toolUpdate(updates); | ||
| expect(update?.status).toBe("completed"); | ||
| expect(update?.rawOutput).toEqual({ | ||
| content: [{ type: "text", text: "mcp payload" }], | ||
| structuredContent: { ok: true }, | ||
| isError: false, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Prefer parameterised tests here
The two it(...) blocks share identical scaffolding — registerToolUse, clearing updates, calling handleUserAssistantMessage, and calling toolUpdate — differing only in the tool name, the presence of tool_use_result, and the expected rawOutput shape. The team's coding standard asks for parameterised tests wherever the same logical flow is exercised with varied inputs. An it.each table that passes toolName, toolUseResult, and expectedRawOutput (nullable) would express this once, making future cases (e.g. a third tool type) a single table row rather than a copied block.
Context Used: Do not attempt to comment on incorrect alphabetica... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agent/src/adapters/claude/conversion/sdk-to-acp.test.ts
Line: 310-341
Comment:
**Prefer parameterised tests here**
The two `it(...)` blocks share identical scaffolding — `registerToolUse`, clearing `updates`, calling `handleUserAssistantMessage`, and calling `toolUpdate` — differing only in the tool name, the presence of `tool_use_result`, and the expected `rawOutput` shape. The team's coding standard asks for parameterised tests wherever the same logical flow is exercised with varied inputs. An `it.each` table that passes `toolName`, `toolUseResult`, and `expectedRawOutput` (nullable) would express this once, making future cases (e.g. a third tool type) a single table row rather than a copied block.
**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Standard tool results (Read, Bash, ...) render from
content, so also copying the full result intorawOutputduplicated large payloads on their way to the renderer. Only MCP tools consume rawOutput (App bridge replay), so emit it solely when an MCP tool result is present.Problem
When PostHog Code runs standard tools like Read or Bash, the tool result payload was being duplicated: once in content (where the renderer actually reads it) and again in rawOutput. For large file reads or substantial bash output, this doubled the serialized payload sent through the renderer pipeline for no reason. Only MCP tools consume rawOutput — the App bridge replays it to display structured MCP results.
Changes
sdk-to-acp.ts: Conditionally include rawOutput in tool_call_update only when an MCP tool result is present (ctx.mcpToolUseResult). Standard tools now emit status and metadata without the redundant rawOutput payload.
sdk-to-acp.test.ts: Added two tests covering both branches:
This is a pure optimization — no behavioral change for consumers.
How did you test this?
Ran pnpm --filter @posthog/agent test — all 690 tests pass, including the 2 new test cases
Ran pnpm biome lint on both changed files — 0 issues
Manually verified the MCP path: rawOutput still carries content, structuredContent, and isError for App bridge replay
Automatic notifications