Skip to content

canAccessThread denies every chat thread, and reads as "no data" rather than "no access" #2963

Description

@Israeltheminer

What happens

canAccessThread (convex/lib/rls/auth/can_access_thread.ts) resolves a thread by reading the threadMetadata table. The rewritten chat pipeline never writes that tableconvex/tasks/internal_mutations.ts:947 is the writer, so task and discussion threads have a row and chat threads do not.

For a chat thread it therefore returns null every time. That is not restricting access; it is denying all of it.

Why it is easy to miss

A query gated on it answers "nothing here" rather than "you cannot see this". Absence of data and absence of permission look identical to the caller, so the surface renders an empty state and nothing errors. Typecheck, type-aware lint and the unit suites stay green throughout, because nothing exercises those queries against a thread that actually exists.

Where it is used

Thread-scoped readers gated on it:

  • convex/approvals/queries.ts — three call sites (~L237, ~L283, ~L329)
  • convex/approvals/human_control_mutations.ts
  • convex/approvals/plan_mutations.ts
  • convex/http.ts
  • convex/lib/rate_limiter/index.ts

Each is correct for a task or discussion thread and silently empty for a chat thread. Some may be unreachable for chat threads today — worth confirming per call site rather than assuming, since the failure mode is invisible either way.

What the chat lane does instead

convex/chat/messages.ts (listMessages) gates on the chat domain's own shape: an org-membership check, then ctx.db.normalizeId('threads', …) plus a match on organizationId and userId, treating a non-undefined lifecycleStatus as gone. Any thread-scoped read that must serve chat threads needs that gate, not canAccessThread.

Suggested direction

Either give canAccessThread a chat-thread branch so one helper covers both thread kinds, or leave it as the task/discussion gate and audit the call sites above for ones that must also serve chat. The first is less likely to rot, since the current split is invisible at the call site.

Worth a test that drives at least one of these queries against a real chat thread — the absence of such a test is the reason this survived.

Activity

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