Converse stream drop always falls back to manual resend, even when the durable event log could confirm it's safe to auto-retry
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 152
- Forks
- 16
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 536
Description
Summary
When the active Converse stream drops mid-run (a transient transport error, e.g. codes.Unavailable), mecatui never auto-resubmits the in-flight prompt. It ends the run, restores the typed text as an editable draft, and requires the user to manually resend. The justification in cmd/mecatui/ui/update.go (around the StreamErrMsg handler, authoritative := isWorkspaceEnrollmentRejection(...)) is:
A transport error has no semantic commit fact. Restore only an unmodified text-only draft; a server-confirmed enrollment rejection is the one case eligible for a later exact-once replay.
This is presented as an inherent limitation, but it's actually a scoping decision that leaves usable ground truth on the table.
What's genuinely true
Prompt (contracts/proto/mecatl/v1/harness.proto:1554) carries only session_id, text, and parts - no client-generated request or idempotency ID. So a blind auto-resend of the same ConverseRequest really is unsafe: the server has no way to distinguish "retry of a message you never received" from "a genuinely new, identical message," and would append and reprocess a second user turn (including re-running any tool calls). Refusing to auto-resend blindly is the correct default given this protocol gap.
What's overstated
"A transport error has no semantic commit fact" is true only from local information. The client already has a way to ask the server whether the prompt landed: the durable event log (StreamSessionEvents). catchUpReplay (cmd/mecatui/client/events.go:195-255) already uses exactly this log to recover missed events when the passive live-feed (StreamSessionLive) reconnects after a drop.
That machinery is not wired into the Converse-stream failure path at all. The StreamErrMsg handler in ui/update.go goes straight to "restore an unmodified draft, make the human decide" without ever checking the session's own history for whether the just-sent prompt was actually committed.
So the accurate claim is "we haven't built the check that would let us know," not "we can't know." Those are different statements, and the code comment asserts the stronger, false one.
Suggested direction
On a StreamErrMsg for the Converse reader (not the live feed) with Transient: true:
- Before restoring the draft, replay the durable event log for the session (the same call
catchUpReplayalready makes) and check whether the just-sent prompt shows up as committed. - If it isn't there, it's safe to auto-resubmit without user intervention.
- If it is there (or the check itself is inconclusive), fall back to today's behavior: restore the draft and let the user decide.
This doesn't close every gap - a request can still be lost between "sent" and "server durably logs it," so a fully robust fix would still want a client-generated request/idempotency ID on Prompt for the narrow remaining window. But it would turn "always ask, every transient blip" into "ask only when it's actually ambiguous," using infrastructure the codebase already has for a nearly identical problem one layer over.
Impact
Low severity, pure UX friction: every transient network blip on the interactive stream (VPN reconnect, brief connectivity loss, etc.) currently forces a manual resend even in the common case where the original send almost certainly succeeded and the server is about to respond. Not data-lossy (the draft is preserved), but avoidable friction with a codebase-native fix.
🤖 Generated with Claude Code
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
Start in cmd/mecatui/ui/update.go at the StreamErrMsg handler and compare its Converse-stream failure path with catchUpReplay in cmd/mecatui/client/events.go. Trace the StreamSessionEvents response and the Prompt definition in contracts/proto/mecatl/v1/harness.proto. Done means transient drops consult the durable session history before deciding between safe retry and draft restoration, with inconclusive checks retaining today’s behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, grpc
- Domain
- backend-api-design, cli
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100