Failed sandbox tool calls arrive without error.message and with a character-indexed rawOutput
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 39.9k
- Forks
- 3.4k
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 232
Description
Problem
When a sandbox tool call fails inside the agent, the tool_call_update frame that reaches PostHog has status: "failed" but no error field. The failure reason is instead placed in rawOutput, and it arrives as a character-indexed object rather than a string:
{
"sessionUpdate": "tool_call_update",
"toolCallId": "toolu_…",
"status": "failed",
"rawOutput": { "0": "I", "1": "n", "2": "p", "3": "u", "4": "t", "5": "V", "6": "a", "7": "l", "8": "i", "9": "d", "10": "a", "11": "t", "12": "i", "13": "o", "14": "n", "15": "E", "16": "r", "17": "r", "18": "o", "19": "r", "20": ":", "21": " ", "22": "[", "…": "…" },
"_meta": { "claudeCode": { "toolName": "AskUserQuestion" } }
}
Observed on a local dogfood run where the agent's first AskUserQuestion call was rejected by the tool runtime with InputValidationError. The same shape is stored in the S3 run log and streamed live, so both replay and live views are affected.
Effect
The web thread renders the row as "Failed" with no reason. ToolActivity only shows a failure line when error.message is present (products/posthog_ai/frontend/components/tool/ToolActivity.tsx), and the generic output renderer cannot display a string that was spread into an object.
Where
Nothing in this repository transforms rawOutput between the sandbox and the browser: the relay activities under products/tasks/backend/temporal/process_task/activities/ and products/tasks/backend/logic/stream/agent_events.py pass frames through, and the frontend reads update.rawOutput as-is (products/posthog_ai/frontend/logics/runStreamLogic.ts, invocationFromToolCallUpdate). The mangling happens in the agent server that adapts Claude Code tool results to ACP session/update frames, most likely where a string error result is spread into an object ({...errorString}).
Expected
For a failed tool call the frame carries:
status: "failed"error: { "message": "<reason>" }with the runtime's error textrawOutputeither omitted or the untouched result value, never a character-indexed object
Acceptance criteria
- A tool call rejected by the runtime (for example an invalid
AskUserQuestionpayload) produces atool_call_updatewitherror.messageset to the rejection text. -
rawOutputon failed frames is a string, an object as returned by the tool, or absent. No{"0": "…", "1": "…"}shapes. - A unit test in the agent server covers a string error result.
- After the fix, the PostHog web thread shows the reason under the failed row with no frontend change.
Out of scope
Linking a failed call to a later retry by the agent. A retry marker would need a runtime-level signal; the frontend will not infer it from adjacency.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the rejected AskUserQuestion call, then trace the frame through products/tasks/backend/temporal/process_task/activities/, products/tasks/backend/logic/stream/agent_events.py, and products/posthog_ai/frontend/logics/runStreamLogic.ts. Inspect the agent-server adapter where Claude Code results become ACP session/update frames and add a unit test for a string error result. Done means failed frames contain error.message, rawOutput is not character-indexed, and ToolActivity renders the reason without frontend changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, react, typescript
- Domain
- backend, frontend, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100