twentyhq/twenty · critical · Error

Cannot migrate: rolePermissionFlag rows reference unknown fl

Error message

Cannot migrate: rolePermissionFlag rows reference unknown flag value(s): ${unknownFlags}

What it means

Thrown by a SLOW instance-level 2.6 migration that backfills permissionFlag rows and links rolePermissionFlag to them. Before writing, it queries core.rolePermissionFlag for any 'flag' value not in the current PermissionFlagType enum (PERMISSION_FLAG_TYPES). If any unknown flag exists, it refuses to migrate and lists them. This is a deliberate fail-fast data-integrity guard: it prevents silently dropping permission grants whose flag the running code no longer recognizes.

Source

Thrown at packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id.ts:23

import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
import { STANDARD_PERMISSION_FLAG_DEFINITIONS } from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant';

const PERMISSION_FLAG_TYPES = Object.values(PermissionFlagType) as string[];

@RegisteredInstanceCommand('2.6.0', 1778235340023, { type: 'slow' })
export class BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand implements SlowInstanceCommand {
  async runDataMigration(dataSource: DataSource): Promise<void> {
    const unknownFlagRows: { flag: string }[] = await dataSource.query(
      `SELECT DISTINCT "flag" FROM "core"."rolePermissionFlag"
       WHERE "flag" <> ALL($1::varchar[])`,
      [PERMISSION_FLAG_TYPES],
    );

    if (unknownFlagRows.length > 0) {
      const unknownFlags = unknownFlagRows.map((row) => row.flag).join(', ');

      throw new Error(
        `Cannot migrate: rolePermissionFlag rows reference unknown flag value(s): ${unknownFlags}`,
      );
    }

    for (const definition of STANDARD_PERMISSION_FLAG_DEFINITIONS) {
      await dataSource.query(
        `INSERT INTO "core"."permissionFlag" (
          "id",
          "workspaceId",
          "applicationId",
          "universalIdentifier",
          "key",
          "label",
          "description",
          "icon",
          "permissionType",
          "createdAt",
          "updatedAt"

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. The thrown message lists the offending flag values — inspect them: SELECT DISTINCT flag FROM core.rolePermissionFlag WHERE flag NOT IN (<current enum>).
  2. If a flag was legitimately renamed, update the rows to the new canonical value before re-running.
  3. If a flag is genuinely obsolete and its grants should be dropped, delete or map those rolePermissionFlag rows intentionally, then re-run the slow migration.
  4. Ensure the deployed twenty-shared/constants matches the release expected by the DB state.

Example fix

-- before: rows reference a removed flag, migration refuses
--   flag = 'CAN_MANAGE_OLD_FEATURE'

-- fix: map to the canonical flag (or delete if obsolete), then re-run:
--   UPDATE core.rolePermissionFlag
--   SET flag = 'CAN_MANAGE_NEW_FEATURE'
--   WHERE flag = 'CAN_MANAGE_OLD_FEATURE';
Defensive patterns

Strategy: validation

Validate before calling

// Run the same guard query before invoking the slow migration:
const unknown = await dataSource.query(
  `SELECT DISTINCT "flag" FROM "core"."rolePermissionFlag" WHERE "flag" <> ALL($1::varchar[])`,
  [Object.values(PermissionFlagType)],
);
if (unknown.length > 0) { /* map or delete offending flags before migrating */ }

Type guard

import { PermissionFlagType } from 'twenty-shared/constants';

const isKnownPermissionFlag = (flag: string): flag is PermissionFlagType =>
  (Object.values(PermissionFlagType) as string[]).includes(flag);

Prevention

When it happens

Trigger: A workspace/instance has rolePermissionFlag rows with a 'flag' value that is not a member of the PermissionFlagType enum in the deployed code — e.g. a flag renamed/removed between releases, or rows inserted by a fork/plugin. The migration halts before the INSERT/UPDATE so no grants are lost.

Common situations: Downgrading to a release that removed a permission flag; running a fork that added custom flags not present in upstream; stale rows from a feature-flag experiment; mismatch between the deployed twenty-shared/constants version and the DB contents.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/74e320ee855300ce. Report an issue: GitHub.