fix(server): open existing chat instead of failing on duplicate bootstrap thread.create - #6
Merged
Conversation
…trap thread.create Bootstrapping a draft is a create-if-absent operation: the client generates the threadId before the thread exists, so a duplicate send, a retry, or an already-promoted draft can target a threadId the server already has. Previously that hard-failed the whole turn with an "already exists and cannot be created twice" invariant, surfacing an error banner. Treat that specific invariant as a no-op in the bootstrap path and continue the turn against the existing thread. The message lands, the thread starts, and the draft route navigates the user into the already-created chat. Co-Authored-By: Claude Opus 4.8 <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 freeto 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.
Problem
Sending the first message on a local draft thread embeds a
bootstrap.createThreadin thethread.turn.startcommand, using athreadIdthe client pre-generated. The server expands that into a realthread.create. If thatthreadIdalready exists server-side (a double-send, a retry, or a draft that was already promoted), therequireThreadAbsentinvariant fires, the whole bootstrap turn fails, and the user sees an error banner:Fix
Bootstrapping a draft is inherently a create-if-absent operation, so the duplicate
thread.createshould be a no-op rather than a hard failure. The bootstrap path now catches that specific invariant, treats it as a no-op, and continues the turn against the existing thread. The message lands, the thread starts, and the draft route's existing navigation logic moves the user into the already-created chat — instead of showing the error.Done server-side because the error only reaches the client as a generic
OrchestrationDispatchCommandErrormessage string; detecting it in the UI would mean fragile string matching and would drop the user's message.Changes
commandInvariants.ts— extracted the "already exists" detail into a shared function and addedisThreadAlreadyExistsInvariantError(error, threadId), a precise predicate (matches on bothcommandType === "thread.create"and the exact detail, so it never swallows a different invariant like a missing project).ws.ts— the bootstrapthread.createdispatch catchesOrchestrationCommandInvariantError, treats the "already exists" case as a no-op, and keepscreatedThread = falseso failure-cleanup never deletes a pre-existing thread.commandInvariants.test.ts— coverage for the new predicate, including negative cases.Verification
pnpm typecheck— clean🤖 Generated with Claude Code