Skip to content

fix(server): open existing chat instead of failing on duplicate bootstrap thread.create - #6

Merged
enisze merged 1 commit into
mainfrom
t3code/open-existing-thread-on-create-error
Jul 29, 2026
Merged

enisze merged 1 commit into
mainfrom
t3code/open-existing-thread-on-create-error

Conversation

@enisze

@enisze enisze commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Problem

Sending the first message on a local draft thread embeds a bootstrap.createThread in the thread.turn.start command, using a threadId the client pre-generated. The server expands that into a real thread.create. If that threadId already exists server-side (a double-send, a retry, or a draft that was already promoted), the requireThreadAbsent invariant fires, the whole bootstrap turn fails, and the user sees an error banner:

Orchestration command invariant failed (thread.create): Thread '…' already exists and cannot be created twice.

Fix

Bootstrapping a draft is inherently a create-if-absent operation, so the duplicate thread.create should 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 OrchestrationDispatchCommandError message 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 added isThreadAlreadyExistsInvariantError(error, threadId), a precise predicate (matches on both commandType === "thread.create" and the exact detail, so it never swallows a different invariant like a missing project).
  • ws.ts — the bootstrap thread.create dispatch catches OrchestrationCommandInvariantError, treats the "already exists" case as a no-op, and keeps createdThread = false so failure-cleanup never deletes a pre-existing thread.
  • commandInvariants.test.ts — coverage for the new predicate, including negative cases.

Verification

  • pnpm typecheck — clean
  • Orchestration suite: 191/191 passing, including the new predicate test

🤖 Generated with Claude Code

…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>
@enisze
enisze merged commit 979e8e6 into main Jul 29, 2026
@enisze
enisze deleted the t3code/open-existing-thread-on-create-error branch July 29, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant