Repository navigation
fix: harden the chat call against malformed events and config - #21
Merged
Merged
Conversation
`started` was the one event read without checking its shape: a missing assistantMessageId or traceId was coerced with String(), so the store got the literal text "undefined" as the bubble id. A second such turn would then overwrite the first turn's bubble in place, and "undefined" would be kept as the trace id for support. It is now held to the same rule as completed and error: a started event without both ids is a broken stream, reported as upstream_error before any handler runs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`api.baseUrl` is used as written and every path is appended as `/v1/chat`, so a value of `/experience/api/` produced `/experience/api//v1/chat`. The API treats that as a different, unknown route and answers 404, which the client showed as an unknown error on every turn with nothing pointing at the cause. The dev proxy had the matching problem: its key matched, but the stripped path lost its leading slash. Trailing slashes are now dropped where the value is read, in the client and in vite.config.ts alike. The old default ended in a slash, so this is a natural thing to type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`completed` was accepted once `content` was an array. Two malformed shapes then reached the store: an `error` field that was not the contract's object, which made the turn count as failed with Retry silently hidden because `retryable` was undefined; and empty `content` with no `error`, which drew an empty assistant bubble with a Copy row and counted as answered. Both are now a broken stream, reported as upstream_error, the same as a completed with no content array. The rule is §5.2: content is a list, error when present has our shape, and empty content only comes with error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The turn runner removed the two copies of the streaming and error code from sendText and sendImage, but each still carried its own copy of "fetch suggestions and map them" afterwards. One helper now, so a change to the suggestion shape is made once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The four findings from the review of #19, one commit each.
startedwithout ids is a broken stream. It was coerced withString(), so a missingassistantMessageIdbecame the bubble id"undefined"and a second such turn overwrote the first bubble. Nowupstream_error, like a malformedcompletedorerror.api.baseUrlmay end in a slash./experience/api/produced/experience/api//v1/chat, a 404 with nothing pointing at the cause. Trailing slashes are dropped where the value is read, in the client and the dev proxy.completedis validated before the store acts on it. Anerrorfield that is not the contract's object, or emptycontentwith noerror, is nowupstream_errorinstead of a silently hidden Retry or an empty bubble with a Copy row.Verified
🤖 Generated with Claude Code