Skip to content

Fix intermittent "Channel access denied. User is not a participant" error#130

Open
bmiller59 wants to merge 2 commits into
mainfrom
bam/user-sync
Open

Fix intermittent "Channel access denied. User is not a participant" error#130
bmiller59 wants to merge 2 commits into
mainfrom
bam/user-sync

Conversation

@bmiller59

Copy link
Copy Markdown
Collaborator

What is in this PR?

Fixes an intermittent "Channel access denied. User is not a participant" error caused by TokenManager adopting tokens that belong to a different user. During cold-start races (e.g. two tabs creating guest sessions simultaneously, or a cookie sync overwriting freshly-issued tokens), SessionManager.userId could diverge from the user the tokens were actually minted for, so channel names like direct-${userId}-${agentId} were built for a user the socket token didn't authorize.

The fix makes TokenManager identity-aware: it tracks the user that owns the current tokens and rejects cross-tab broadcasts and cookie-sync tokens that belong to a different user.

Changes in the codebase

  • utils/TokenManager.ts: Added _ownerUserId to track the user owning the current tokens. setTokens(tokens, { broadcast?, userId? }) now records the owner on authoritative writes and includes userId in TOKENS_REFRESHED broadcasts. Added guards in _handleTabMessage (adopt a broadcast only when message.userId === _ownerUserId) and _syncFromCookie (ignore cookie tokens whose userId differs from the current owner, triggering a refresh instead). clearTokens() resets the owner.
  • utils/Helpers.ts: Api.SetTokens(...) takes an optional userId and forwards it through setTokensFromStrings.
  • utils/SessionManager.ts / pages/login.tsx: Thread userId through the three authoritative token-write call sites (cookie restore, guest create, login).
  • __tests__/utils/TokenManager.test.ts / SessionManager.test.ts: Updated for the new setTokens signature; added coverage for cross-user broadcast/cookie rejection and authoritative owner updates.
  • __tests__/pages/assistant.test.tsx: Fixed a pre-existing failing test (displays real-time messages from any agent direct channel). It grabbed .at(-1) of the message:new listeners, which collided with the Transcript sidebar's own listener; now it dispatches message:new to all registered handlers, faithfully simulating a socket broadcast.
  • SESSION_AND_TOKEN_SYSTEM.md: Documented token identity ownership, updated the refresh-flow diagrams/pseudocode, the two-tab race scenario, console-log reference, and added a "Channel access denied" pitfall entry.

Documentation and automated testing

Did you:

  • document any breaking changes in your commit messages? (none — setTokens/SetTokens changes are backward compatible via optional params)
  • document your changes as comments in the code? Use TSDoc format where appropriate.
  • update the README and docs to be clear and easy to use for end users and developers? (updated SESSION_AND_TOKEN_SYSTEM.md)
  • add and/or update automated tests?
  • update team documentation of any new or changed environment variables? (n/a)
  • add a What's New entry in content/whatsNew.ts for new features? (n/a — internal bug fix)

Testing this PR

  • Open the app in two tabs simultaneously from a cold start (no existing session) and confirm neither tab hits "Channel access denied / User is not a participant".
  • Log in as a user in one tab while another tab holds a guest session; confirm the guest tab does not adopt the logged-in user's tokens (watch console for the identity-guard warnings).
  • Confirm normal token refresh still propagates across tabs for the same user.
  • All tests should app

Additional information

Known follow-up (out of scope): the gap-reconnect re-fetch in pages/assistant.tsx (~line 611) does a full setAssistantMessages replace and could drop a real-time message that arrives mid-fetch — worth hardening to a merge later.

bmiller59 added 2 commits June 5, 2026 14:00
TokenManager stored tokens with no notion of which user they belonged
to, so cross-tab TOKENS_REFRESHED broadcasts and cookie syncs were
adopted unconditionally. A tab could authenticate as user B while
building direct-channel names for user A, causing intermittent
"User is not a participant" errors (most likely when two tabs create
distinct guest sessions at once).

Track an _ownerUserId in TokenManager: authoritative local writes (guest
creation, login, cookie restore) establish the owner, while remote
adoptions (broadcast, _syncFromCookie) are only accepted when their
userId matches. Thread userId through Api.SetTokens and the three
authoritative call sites, and document the behavior.
The test grabbed the last message:new handler (.at(-1)), which collided with
the Transcript sidebar's listener. Invoke all registered handlers to faithfully
simulate the socket broadcast so the assistant page's handler runs.
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

You must set up Two-Factor Authentication before accessing this team.

View Documentation: https://vercel.com/docs/two-factor-authentication

@bmiller59 bmiller59 changed the title Bam/user sync Fix intermittent "Channel access denied. User is not a participant" error Jun 5, 2026
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