lobehub/lobehub · error · TRPCError

FORBIDDEN

FORBIDDEN

Error message

Topic comment resource not found

What it means

Permission gate at the entry of assertTopicCommentReadAccess. The caller lacks TOPIC_COMMENT_READ with either 'all' or 'owner' scope in this workspace, so the read is denied before the topic is even queried. hideExistence defaults to FORBIDDEN, but callers that want to hide resource existence (e.g. unauthenticated shared-link readers) pass hideExistence: true to downgrade to NOT_FOUND.

Source

Thrown at apps/server/src/routers/lambda/_helpers/topicCommentAccess.ts:26

import { assertCanViewTopicTargets } from './conversationResourceGuard';

export const assertTopicCommentReadAccess = async (params: {
  db: LobeChatDatabase;
  grantedPermissions?: readonly string[];
  hideExistence?: boolean;
  topicId: string;
  userId: string;
  workspaceId: string;
}) => {
  const permissions = await getWorkspaceScopedPermissionMatches({
    action: 'TOPIC_COMMENT_READ',
    db: params.db,
    grantedPermissions: params.grantedPermissions,
    userId: params.userId,
    workspaceId: params.workspaceId,
  });
  if (!permissions.hasAllScope && !permissions.hasOwnerScope) {
    throw new TRPCError({
      code: params.hideExistence ? 'NOT_FOUND' : 'FORBIDDEN',
      message: 'Topic comment resource not found',
    });
  }

  const [topic] = await params.db
    .select({ id: topics.id })
    .from(topics)
    .where(and(eq(topics.id, params.topicId), eq(topics.workspaceId, params.workspaceId)))
    .limit(1);
  if (!topic) {
    throw new TRPCError({ code: 'NOT_FOUND', message: 'Topic comment resource not found' });
  }

  try {
    await assertCanViewTopicTargets(
      {
        db: params.db,

View on GitHub (pinned to 10f24d7ade)

Solutions

  1. Grant the user TOPIC_COMMENT_READ (all or owner scope) for the workspace via the workspace permission admin.
  2. Confirm the caller's role — viewers may need promotion to member.
  3. If the caller is the topic creator and should inherently have read access, verify the permission resolution path includes creator-scope fallback.

Example fix

// before: caller has no TOPIC_COMMENT_READ grant
await trpc.topicComment.list.query({ topicId, workspaceId }); // FORBIDDEN

// after: admin grants the permission
await admin.grantWorkspacePermission(userId, workspaceId, 'TOPIC_COMMENT_READ', 'all');
Defensive patterns

Strategy: validation

Validate before calling

const canReadComments = (grantedPermissions: readonly string[] | undefined) =>
  grantedPermissions?.some((p) => p.includes('TOPIC_COMMENT_READ')) ?? false;
if (!canReadComments(session.grantedPermissions)) {
  // hide the comment UI instead of letting the call fail
}

Type guard

null

Try / catch

try {
  await trpc.topicComment.list.query({ topicId, workspaceId });
} catch (e) {
  if (e.code === 'FORBIDDEN') hideCommentUI();
}

Prevention

When it happens

Trigger: Calling a topic-comment read procedure (list/get comments on a topic) when the workspace caller's grantedPermissions do not include a TOPIC_COMMENT_READ grant with workspace-wide or owner scope. Triggered by viewers/members without the comment-read permission, or by callers from a different workspace.

Common situations: A workspace viewer (read-only role) trying to read comments when only members have the grant; permission grants not yet provisioned after a workspace role change; a stale permission cache after a role downgrade.

Related errors


AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12). Data as JSON: /api/errors/c8d86219b835eab3. Report an issue: GitHub.