fix(trpc): retry chat session insert without v2WorkspaceId on FK violation
Fixes #6230
Bug
Sending a message in a chat pane for a "local" entry (the type: "main" workspace, i.e. the repo checkout itself) fails with a database error, in every repo. Worktree workspaces work — only "local" entries fail.
Root cause
chat.createSession (packages/trpc/src/router/chat/chat.ts) inserted chat_sessions.v2_workspace_id directly from the client-supplied input.v2WorkspaceId. That column has an FK to cloud v2_workspaces (chat_sessions_v2_workspace_id_v2_workspaces_id_fk).
Since local-first (#5731), workspaces are host-owned: the main workspace is minted locally with a fresh UUID on every boot (runMainWorkspaceSweep → ensureMainWorkspace) and is never mirrored to the cloud v2_workspaces table. The FK can therefore never be satisfied for those ids, and the insert throws on every message — surfacing the raw query in the chat pane.
The legacy REST bootstrap (apps/api/src/app/api/chat/[sessionId]/route.ts) already carries a defensive retry-without-workspaceId fallback for this exact failure class; the tRPC path had none.
Fix
Extract the session insert into create-chat-session.ts and retry without v2WorkspaceId when the insert fails with a foreign-key error — mirroring the REST route's shouldRetryWithoutWorkspaceId behavior. The retry runs in a fresh transaction because a failed INSERT aborts the surrounding Postgres transaction (current transaction is aborted), so re-inserting inside the same one would fail regardless.
The resulting row keeps v2WorkspaceId NULL for local-first workspaces — the same degraded-but-functional state the REST path produces.
Tests
Added packages/trpc/src/router/chat/utils/create-chat-session.test.ts (6 cases):
- insert with
v2WorkspaceIdsucceeds and returns the txid - FK violation → retries without
v2WorkspaceId - generic foreign-key error → retries
- non-FK errors are propagated
onConflictDoNothingconflict →{ txid: null }- FK violation logs exactly once
bun test: 6 pass, 0 fail. Package typecheck: clean. Biome check: clean.
Note: #5853 (bot draft, #5852) targets the same FK with an up-front existence check; this PR takes the retry approach already established in the REST route.
Summary by cubic
Retries chat session insert without v2WorkspaceId only when the v2_workspace_id FK rejects it, so messages from the local “main” workspace send. Previously packages/trpc wrote the client’s v2WorkspaceId and surfaced FK errors; now it retries in a fresh transaction and stores a null v2WorkspaceId for local sessions, matching the REST route.
- Extracts
createChatSessioninpackages/trpc/src/router/chat/utils/create-chat-session.tsand uses it frompackages/trpc/src/router/chat/chat.ts. - Retries only when the error matches
chat_sessions_v2_workspace_id_v2_workspaces_id_fkor a v2_workspace_id FK violation; does not retry on unrelated FK or column errors. - Uses a new transaction for the retry; on conflict, returns
{ txid: null }. - Hardens error handling: safe message parsing avoids calling toLowerCase on undefined; retry log emits a fixed reason without raw ids or the DB error.
- Tests cover success, targeted FK retry, no-retry cases, fresh-transaction fallback, conflict handling, and a single sanitized warning.
Summary by CodeRabbit
-
Bug Fixes
- Improved chat-session creation reliability when legacy workspace references are unavailable.
- Added targeted retry handling for recognized workspace-reference constraint errors.
- Improved handling of session conflicts, transaction identifiers, and unrelated database failures.
- Ensured retries use a fresh transaction and produce clear warning logs.
-
Tests
- Added coverage for successful creation, retry behavior, conflicts, transaction isolation, warning logs, and error propagation.