[fix] Wait for a request's arguments before calling it malformed [AGE-4095] - #6064
[fix] Wait for a request's arguments before calling it malformed [AGE-4095]#6064ashrafchowdury wants to merge 1 commit into
Conversation
…rmed
The runner surfaces a tool call before its args exist and refreshes them
incrementally, so a request_input part passes through null, {} and partial
payloads before the real one arrives. ElicitationWidget validated whatever it
saw at mount and auto-settled a permanent degradation error, so a valid
question rendered as "Couldn't render this request." and was lost (#5949).
Gate the degradation settle and the parked notice on the turn having stopped
streaming, which is the point at which the args are final. A genuinely
malformed payload still degrades as soon as the turn parks, so nothing hangs.
This matches UnhandledClientTool, which already refuses to auto-settle while
the turn is streaming.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
Walkthrough
ChangesStreaming elicitation handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This localized change defers malformed-request handling until request arguments are complete, preventing valid forms from being replaced by error chips while preserving existing malformed-request behavior; no actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant AgentMessage
participant ClientToolPart
participant ElicitationWidget
participant Settlement
AgentMessage->>ClientToolPart: Pass turnStreaming
ClientToolPart->>ElicitationWidget: Forward streaming state and input
ElicitationWidget->>ElicitationWidget: Wait for final input
ElicitationWidget->>Settlement: Settle degradation after streaming ends
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Railway Preview Environment
Updated at 2026-08-16T16:00:45.620Z |
Context
When an agent asked a question mid-run, the card sometimes rendered as an amber "Couldn't render this request." chip instead of the form. The question was lost, the agent carried on as if it had been answered, and a reload replayed the same chip because the failure was already saved.
The recorded error said
payload is not an object, yet the savedinputheld a perfectly good payload. Both were true because the parse ran before the arguments arrived.The runner surfaces a tool call before its arguments exist and fills them in afterwards, and it does so on purpose (
services/runner/src/tracing/otel.ts: the call "MUST surface before any approval/result for this id", so the args are refreshed later, "never by delaying this"). The args then land incrementally, as a growing partial parse. So a liverequest_inputpart passes through a sequence of shapes:ElicitationWidgetvalidated whichever shape it happened to mount on and auto-settled a permanent error. Whether it caught a placeholder depended on whereuseChat's 50ms throttle boundary fell against the stream, which is why the same session could fail once and render fine the next time.Changes
The widget now judges the payload only once the turn stops streaming, which is the point at which the args are final. Both the degradation settle and the parked "needs attention" notice wait for it.
A narrower check does not work. Testing
input != nullmisses the{}announce, and testingstate === "input-available"misses every partial, because the part is alreadyinput-availablefrom the first frame onward.turnStreamingthreads fromAgentMessage(which already heldisStreaming) throughClientToolPartto the widget, matching how the siblingdegradedEarlierInTurnflag is already passed. The default is safe: a caller that omits it degrades exactly as before, so nothing can hang.This also brings elicitation in line with a rule the codebase already had.
UnhandledClientToolcan only mount when the turn is not streaming, so it never auto-settles mid-stream. Elicitation was the exception.A genuinely malformed payload still degrades the moment the turn parks.
Tests / notes
ElicitationWidget.degradation.test.tsxdrives the real frame sequence above through the real parser (only the chrome is mocked) and pins that the widget waits. I confirmed it is not vacuous: with the gate disabled, the three cases covering the fix fail and the two covering unchanged behaviour still pass.pnpm vitest run src/components/AgentChatSlice: 490 passed, 1 skipped.pnpm lint-fixclean.client_toolemit sites already carry complete args. The bug was the frontend reading "not finished" as "malformed".degradedEarlierInTurnonly flips once a part carries the error text. That is two error settles, not a resume loop, and fixing it properly would pull payload validation up intoAgentMessage.ConnectToolWidgetreadsmeta.inputunder the same assumption and does not take this flag. It degrades gracefully (a briefly wrong label, no auto-settle), so it is not broken today. It is the second widget that would want this, which is when movinginputFinalontoClientToolMetastarts to pay off.What to QA
discover_toolsshows the stepper form ("1. GitHub repository", "Review posture", "Risk focus"), never an amber "Couldn't render this request." chip.