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
- Read the wrapped error.message — it carries the native module's complaint (often a field name or enum value).
- Confirm the native module version matches the TS schema (rebuild/reinstall native bindings after schema changes).
- Log the full input (with secrets redacted) when this fires so you can reproduce the failing evaluation shape.
- Add a TS-level validator (zod/io-ts) in front of evaluatePermissionV1 to catch malformed inputs before they hit native code.
- 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
- Keep native module and TS schema versions in lockstep; rebuild native bindings after schema changes.
- Validate input shape with zod/io-ts before hitting native code.
- Fail closed (deny) on evaluator errors — never grant on uncertainty.
- Log the full input (redacted) when this fires to enable reproduction.
- Audit role/decision rows for nulls or unknown enum values feeding the evaluator.
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.