toeverything/AFFiNE · critical · InternalServerError

internal_server_error

internal_server_error

Error message

An internal error occurred.

What it means

InternalServerError (code=internal_server_error) thrown by PermissionService.evaluate when the native evaluatePermissionV1 call throws. evaluatePermissionV1 delegates to a native (Rust/N-API) module; any panic/type mismatch/missing field in the input surfaces here, wrapped so the raw native message becomes the error's message. This is a true 500 — the inputs were structurally invalid for the evaluator or the native module is broken.

Source

Thrown at packages/backend/server/src/core/permission/service.ts:81

    private readonly sqlPredicate = new PermissionSqlPredicateBuilder(),
    @Optional()
    private readonly workspacePolicy?: WorkspacePolicyService
  ) {}

  docReadableSqlPredicate(input: {
    userId: string;
    workspaceId: string;
    action: DocAction;
    docIdColumn?: Prisma.Sql;
  }) {
    return this.sqlPredicate.docReadableSql(input);
  }

  evaluate(input: PermissionEvaluationInputV1) {
    try {
      return evaluatePermissionV1(input);
    } catch (error) {
      throw new InternalServerError(
        error instanceof Error ? error.message : undefined
      );
    }
  }

  async workspacePermissions(input: {
    userId?: string;
    workspaceId: string;
    actions: PermissionWorkspaceAction[];
    allowLocal?: boolean;
  }) {
    const output = await this.evaluateLoaded({
      userId: input.userId,
      workspaceId: input.workspaceId,
      workspaceActions: input.actions,
      allowLocal: input.allowLocal,
    });
    return {

View on GitHub (pinned to 26c515e050)

Solutions

  1. Read the wrapped error.message — it carries the native module's complaint (often a field name or enum value).
  2. Confirm the native module version matches the TS schema (rebuild/reinstall native bindings after schema changes).
  3. Log the full input (with secrets redacted) when this fires so you can reproduce the failing evaluation shape.
  4. Add a TS-level validator (zod/io-ts) in front of evaluatePermissionV1 to catch malformed inputs before they hit native code.
  5. If isolated to one user/doc, inspect their role/decision rows for nulls or unknown enum values.

Example fix

// before
try {
  return evaluatePermissionV1(input);
} catch (error) {
  throw new InternalServerError(error instanceof Error ? error.message : undefined);
}

// after — validate input shape before native call, log structured context
evaluate(input: PermissionEvaluationInputV1) {
  const parsed = PermissionEvaluationInputV1Schema.safeParse(input);
  if (!parsed.success) {
    throw new BadRequest(`Invalid permission input: ${parsed.error.message}`);
  }
  try {
    return evaluatePermissionV1(parsed.data);
  } catch (error) {
    this.logger.error('native permission evaluation failed', { input, error });
    throw new InternalServerError(error instanceof Error ? error.message : undefined);
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { z } from 'zod';
// Define a schema matching PermissionEvaluationInputV1 and parse before native call
const InputSchema = z.object({
  userId: z.string(),
  workspaceId: z.string(),
  action: z.string(),
  docs: z.array(z.object({
    docId: z.string(),
    effectiveRole: z.string().optional(),
    decisions: z.array(z.any()),
  })),
});
function safeEvaluate(input: unknown) {
  const parsed = InputSchema.safeParse(input);
  if (!parsed.success) throw new Error(`Bad permission input: ${parsed.error.message}`);
  return evaluatePermissionV1(parsed.data);
}

Type guard

function isPermissionInternalError(e: unknown): boolean {
  return e instanceof Error && (e as any).code === 'internal_server_error';
}

Try / catch

try {
  return permissionService.evaluate(input);
} catch (e) {
  if (isPermissionInternalError(e)) {
    logger.error('permission eval failed', { input, cause: e.message });
    // fail closed: deny rather than allow on evaluator failure
    return { allowed: false, reason: 'evaluator-error' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a PermissionEvaluationInputV1 with an unexpected role/action value, a missing docs array entry, malformed decision objects, or a shape the native evaluator doesn't recognize. Also fires if the native module failed to load or panicked on a specific input.

Common situations: A new permission role/action added to TS types but not to the native evaluator (version skew between native build and TS). Bad data from the DB feeding into the evaluator (null where a struct is expected). Native module built against a different schema. Memory/panic in the native lib under a specific input.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/99e1224299b82f5c. Report an issue: GitHub.