{"record":{"id":"74e320ee855300ce","repo":"twentyhq/twenty","slug":"cannot-migrate-rolepermissionflag-rows-reference","errorCode":null,"errorMessage":"Cannot migrate: rolePermissionFlag rows reference unknown flag value(s): ${unknownFlags}","messagePattern":"Cannot migrate: rolePermissionFlag rows reference unknown flag value\\(s\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"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","lineNumber":23,"sourceCode":"import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';\nimport { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';\nimport { STANDARD_PERMISSION_FLAG_DEFINITIONS } from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant';\n\nconst PERMISSION_FLAG_TYPES = Object.values(PermissionFlagType) as string[];\n\n@RegisteredInstanceCommand('2.6.0', 1778235340023, { type: 'slow' })\nexport class BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand implements SlowInstanceCommand {\n  async runDataMigration(dataSource: DataSource): Promise<void> {\n    const unknownFlagRows: { flag: string }[] = await dataSource.query(\n      `SELECT DISTINCT \"flag\" FROM \"core\".\"rolePermissionFlag\"\n       WHERE \"flag\" <> ALL($1::varchar[])`,\n      [PERMISSION_FLAG_TYPES],\n    );\n\n    if (unknownFlagRows.length > 0) {\n      const unknownFlags = unknownFlagRows.map((row) => row.flag).join(', ');\n\n      throw new Error(\n        `Cannot migrate: rolePermissionFlag rows reference unknown flag value(s): ${unknownFlags}`,\n      );\n    }\n\n    for (const definition of STANDARD_PERMISSION_FLAG_DEFINITIONS) {\n      await dataSource.query(\n        `INSERT INTO \"core\".\"permissionFlag\" (\n          \"id\",\n          \"workspaceId\",\n          \"applicationId\",\n          \"universalIdentifier\",\n          \"key\",\n          \"label\",\n          \"description\",\n          \"icon\",\n          \"permissionType\",\n          \"createdAt\",\n          \"updatedAt\"","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/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#L5-L41","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["The thrown message lists the offending flag values — inspect them: SELECT DISTINCT flag FROM core.rolePermissionFlag WHERE flag NOT IN (<current enum>).","If a flag was legitimately renamed, update the rows to the new canonical value before re-running.","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.","Ensure the deployed twenty-shared/constants matches the release expected by the DB state."],"exampleFix":"-- before: rows reference a removed flag, migration refuses\n--   flag = 'CAN_MANAGE_OLD_FEATURE'\n\n-- fix: map to the canonical flag (or delete if obsolete), then re-run:\n--   UPDATE core.rolePermissionFlag\n--   SET flag = 'CAN_MANAGE_NEW_FEATURE'\n--   WHERE flag = 'CAN_MANAGE_OLD_FEATURE';","handlingStrategy":"validation","validationCode":"// Run the same guard query before invoking the slow migration:\nconst unknown = await dataSource.query(\n  `SELECT DISTINCT \"flag\" FROM \"core\".\"rolePermissionFlag\" WHERE \"flag\" <> ALL($1::varchar[])`,\n  [Object.values(PermissionFlagType)],\n);\nif (unknown.length > 0) { /* map or delete offending flags before migrating */ }","typeGuard":"import { PermissionFlagType } from 'twenty-shared/constants';\n\nconst isKnownPermissionFlag = (flag: string): flag is PermissionFlagType =>\n  (Object.values(PermissionFlagType) as string[]).includes(flag);","tryCatchPattern":null,"preventionTips":["Keep rolePermissionFlag.flag values aligned with the PermissionFlagType enum of the deployed release.","On downgrade, map or remove flags that the older release does not know before running the slow migration.","Never insert custom flag values from forks/plugins without adding them to the enum."],"tags":["migration","instance-command","slow-migration","data-integrity","permission-flag","fail-fast"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}