Skip to content

fix(platform): scope chat thread reads to the active org#2170

Merged
Israeltheminer merged 2 commits into
mainfrom
fix/chat-active-org-scoping
Jun 26, 2026
Merged

fix(platform): scope chat thread reads to the active org#2170
Israeltheminer merged 2 commits into
mainfrom
fix/chat-active-org-scoping

Conversation

@Israeltheminer

Copy link
Copy Markdown
Collaborator

Problem

Switching organizations while a chat thread was open kept rendering the previous org's chat. Two causes compounded:

  • Backend: the thread by-id queries authorized against the thread's own org (any org the user still belonged to), never the org the user was acting in. A member of both org A and org B kept access to org A's thread after switching to B.
  • Frontend: those query keys weren't org-scoped, so the org-switch cache purge in switching.tsx couldn't drop them and stale data lingered.

A context/correctness bug for multi-org members — tenant isolation was never broken (non-members could not read the data).

Fix

  • canAccessThread now enforces the supplied active org: a thread that doesn't belong to it returns null instead of falling back to membership of the thread's own org. Callers that omit the hint (internal/system) keep prior behaviour.
  • The eight by-id thread queries (getThreadMeta, getThreadMessages, getThreadMessagesStreaming, isThreadGenerating, getThreadShareStatus, getThreadForkInfo, getArenaThreadPair, getFailedMessageErrors) and setThreadCanvasState take the active organizationId; every caller threads it through (route $id via useOrganizationId, or in-scope props). This also makes the query keys org-scoped, so the switch-time cache purge catches them.

Verification

  • tsc --noEmit (whole platform): 0 errors
  • canAccessThread units: 21/21 (incl. a rewritten test that previously locked in the buggy behaviour + a new shared-branch denial test)
  • server thread tests: 193/193
  • client tests (use-message-processing, share/export-chat): 68/68

Scope

First of a sequenced set: the same active-org-coherence gap exists for projects, tasks, automations, documents/knowledge/prompts/conversations (their by-id detail queries authorize against the entity's own org too). Those follow as separate focused PRs, plus a frontend switching.tsx change to reset resource-detail subpaths on switch.

Switching org while a thread was open kept rendering the previous org's
chat. The thread by-id queries authorized against the thread's OWN org
(any org the user still belonged to), never the org the user was acting
in, and their query keys weren't org-scoped so stale data survived the
switch.

canAccessThread now enforces the supplied active org: a thread that does
not belong to it returns null instead of falling back to membership of
the thread's own org. The eight by-id thread queries (getThreadMeta,
getThreadMessages, getThreadMessagesStreaming, isThreadGenerating,
getThreadShareStatus, getThreadForkInfo, getArenaThreadPair,
getFailedMessageErrors) and setThreadCanvasState now take the active
organizationId, and every caller threads it through (route $id via
useOrganizationId or in-scope props). That also makes the query keys
org-scoped, so the org-switch cache purge catches them.

Callers that omit the hint (internal/system) keep prior behaviour, so
tenant isolation is unchanged - this only closes the active-org
coherence gap for members of multiple orgs.
@Israeltheminer Israeltheminer force-pushed the fix/chat-active-org-scoping branch from f032485 to 374f14f Compare June 26, 2026 19:24
@Israeltheminer Israeltheminer merged commit d98aed1 into main Jun 26, 2026
67 of 69 checks passed
@Israeltheminer Israeltheminer deleted the fix/chat-active-org-scoping branch June 26, 2026 22:02
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