Skip to content

Chat session creation fails for "local" workspace — workspace_id inserted as default, violating NOT NULL #6230

Open

Description

@admin-laicadev

What happened

Selecting the "local" entry (the root repo, not a worktree workspace) in the desktop app and sending a chat message fails with a database error. The insert into chat_sessions only sets v2_workspace_id and leaves the legacy workspace_id column as default, which the schema rejects. Worktree workspaces work fine — only "local" entries fail, and they fail in every repo. This predates 1.19.0; I saw the same on 1.18.x.

Failed query

insert into "chat_sessions" ("id", "organization_id", "created_by", "workspace_id",
"v2_workspace_id", "title", "last_active_at", "created_at", "updated_at")
values ($1, $2, $3, default, $4, default, default, default, default)
on conflict do nothing returning "id"
params: b734d77d-2f06-47e3-bec8-21fab96418d2, f406cca2-1419-490d-ad38-b63c61f9f1a3,
7db378a4-66a1-448a-a86f-8155a8b9a0cf, 25433225-6c6a-4422-b142-c705a7ad807d

Steps to reproduce

  1. Open the desktop app, pick any repo
  2. Select the "local" entry (root repo) instead of a worktree workspace
  3. Type any message and send
  4. Error appears; no chat session is created

Environment

  • Superset 1.19.0 (app + CLI)
  • macOS (Darwin arm64)

Activity

  1. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor

    Summary

    Sending the first message in a chat pane for a "local" entry fails with a database error while worktree workspaces work. "local" is the display name for a type: "main" workspace (apps/desktop/src/renderer/utils/getV2WorkspaceDisplayName/getV2WorkspaceDisplayName.ts:15), i.e. the repo checkout itself. The pasted query is a byte-for-byte match for the insert in the cloud tRPC chat.createSession mutation, so the failure is on the cloud side, not in the desktop app. I could not reproduce this against a live DB (read-only triage), but I believe the reported diagnosis is wrong and the real cause is the v2_workspace_id foreign key, not a NOT NULL violation on workspace_id.

    Affected code

    • packages/trpc/src/router/chat/chat.ts:42-60 — the insert in the report. It sets only id, organizationId, createdBy, v2WorkspaceId; every other column falls back to default, which produces exactly the SQL in the issue (... values ($1, $2, $3, default, $4, default, ...) on conflict do nothing returning "id"). There is no try/catch, so the raw driver error (query text included) propagates to the client — which is why the user sees the query.
    • packages/db/src/schema/schema.ts:685-714 — chat_sessions. Note workspaceId at :695 has no .notNull(), and v2WorkspaceId at :698 references v2_workspaces.
    • Caller: apps/desktop/.../ChatPane/hooks/useWorkspaceChatController/useWorkspaceChatController.ts:41-50 and :129-158 (getOrCreateSession), invoked on send from ChatPaneInterface.tsx:529.

    Ruled out: the NOT NULL reading

    chat_sessions.workspace_id is nullable everywhere I can check:

    • added as a plain nullable uuid in packages/db/drizzle/0021_add_workspace_id_to_chat_sessions.sql:1, never altered afterwards (no other ALTER TABLE "chat_sessions" touches it),
    • "notNull": false in the current snapshot packages/db/drizzle/meta/0063_snapshot.json:3609-3614,
    • no .notNull() in the schema at schema.ts:695.

    So omitting workspace_id is legal, and the same insert shape succeeds for worktrees — which it wouldn't if the column were NOT NULL.

    Suspected root cause: FK chat_sessions_v2_workspace_id_v2_workspaces_id_fk

    Workspaces became host-owned/local-first (#5731) and are no longer mirrored to cloud v2_workspaces:

    • packages/host-service/src/trpc/router/project/utils/ensure-main-workspace.ts:58-62 — "Fully local — workspaces have no cloud mirror." The main workspace id is minted by insertLocalWorkspace into host SQLite only (:108-116).
    • packages/host-service/src/workspaces/local-workspace-store.ts:28-32 — "Workspaces have no cloud mirror since local-first (feat(desktop,host-service): local-first projects — host.db owns projects, workspace cloud sync retired #5731), so the cloud capture in v2Workspace.create/delete never fires for local rows."
    • The dual-write reconciler is gone: pushWorkspaceCreateToCloud and workspace-cloud-sync.ts no longer exist in packages/host-service/src/; grepping v2Workspace.create outside plans/ finds only tests and apps/web/src/app/workspaces/page.tsx.

    Meanwhile the FK is still in place — packages/db/drizzle/meta/0063_snapshot.json:3738-3750 — and dropping it is explicitly still pending work: plans/offline-first-workspace-follow-ups.md:80-81 ("Neon migration (user-run): drop chat_sessions.v2WorkspaceId FK → plain uuid tag, then drop v2_workspaces").

    That combination fails any insert whose v2_workspace_id has no cloud row, with 23503 insert or update on table "chat_sessions" violates foreign key constraint "chat_sessions_v2_workspace_id_v2_workspaces_id_fk".

    Why worktrees still work for the reporter but "local" never does: worktrees created during the dual-write era still have their v2_workspaces rows (that era is why packages/host-service/src/runtime/workspace-backfill.ts:34-65 can still read them back from cloud). Main workspaces, by contrast, are (re)created locally with a fresh UUID by runMainWorkspaceSweep → ensureMainWorkspace on boot for every project (packages/host-service/src/runtime/main-workspace-sweep.ts:16-33), so their ids were never in cloud — hence "fails in every repo". If this is right, a newly created worktree should fail the same way; that's the cheapest discriminating test.

    Corroborating evidence that this failure mode is known: the legacy REST bootstrap already carries a defensive fallback for precisely this class of error — apps/api/src/app/api/chat/[sessionId]/route.ts:15-24 (shouldRetryWithoutWorkspaceId, matching "foreign key") and the retry at :85-102. The tRPC path has no equivalent.

    Same crash should hit the agent/automation path, which uses the same mutation: packages/host-service/src/trpc/router/agents/agents.ts:259-262.

    Reproduction

    Manual (needs a DB where the FK still exists):

    1. Create a new project in the desktop app, or delete the host's main workspace row so the boot sweep re-mints it — either way the type='main' row gets a fresh host-minted UUID.
    2. Select "local", send a message → getOrCreateSession → chat.createSession → error.
    3. Confirm with select id from v2_workspaces where id = '<the 4th param, e.g. 25433225-6c6a-4422-b142-c705a7ad807d>'; — I expect 0 rows. Also create a brand-new worktree and send a message there; I expect it to fail identically, which would confirm the cause is "no cloud row", not "type = main".

    Please also paste the raw Postgres error (SQLSTATE + constraint name) rather than just the query — that single line settles FK-vs-NOT-NULL definitively.

    Automated sketch (bun:test, against a throwaway Postgres branch with the migrations applied):

    // expect 23503 on chat_sessions_v2_workspace_id_v2_workspaces_id_fk
    await expect(
      db.insert(chatSessions).values({
        id: randomUUID(),
        organizationId: seededOrgId,
        createdBy: seededUserId,
        v2WorkspaceId: randomUUID(), // host-minted id, absent from v2_workspaces
      }).onConflictDoNothing(),
    ).rejects.toThrow(/chat_sessions_v2_workspace_id_v2_workspaces_id_fk/);

    Suggested fix direction

    The principled fix is the already-planned migration: drop chat_sessions_v2_workspace_id_v2_workspaces_id_fk and keep v2_workspace_id as a plain uuid tag (plans/offline-first-workspace-follow-ups.md:80-81), i.e. remove the .references(() => v2Workspaces.id, ...) in schema.ts:698-700 and regenerate. The FK is unsatisfiable by construction now that the host owns workspace identity; dropping v2_workspaces itself can stay gated on the rest of R3.

    Two notes on stopgaps:

    • Don't copy the REST fallback verbatim (retry with v2WorkspaceId nulled). The pane's session list filters on eq(chatSessions.v2WorkspaceId, workspaceId) (useWorkspaceChatController.ts:74-84), so a null-tagged session would be created but then vanish from the picker and history — a confusing second bug.
    • If the FK must survive for now, the alternative is restoring a cloud upsert for host-minted workspaces (call v2Workspace.create with the local id, including type: 'main') before chat.createSession. That reintroduces the dual-write the local-first work deliberately removed, so I'd only do it as a hotfix.

    Independently of which route is taken, chat.createSession should wrap the insert and return a TRPCError with a human-readable message instead of leaking the driver's query text to the UI, and it currently does not verify that v2WorkspaceId belongs to ctx.activeOrganizationId — worth addressing in the same change since dropping the FK removes the last implicit check on that column.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions