{"record":{"id":"7ad70c4c2e5e1656","repo":"twentyhq/twenty","slug":"backfillmetadataoverrides-isactive-changed-on","errorCode":null,"errorMessage":"BackfillMetadataOverrides: \"isActive\" changed on \"core\".\"${table}\" (${activeCountBefore} -> ${activeCountAfter}), aborting.","messagePattern":"BackfillMetadataOverrides: \"isActive\" changed on \"core\"\\.\"(.+?)\" \\((.+?) -> (.+?)\\), aborting\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-slow-1782986476000-backfill-metadata-overrides.ts","lineNumber":23,"sourceCode":"\nconst TABLES = ['objectMetadata', 'fieldMetadata'] as const;\n\n@RegisteredInstanceCommand('2.19.0', 1782986476000, { type: 'slow' })\nexport class BackfillMetadataOverridesSlowInstanceCommand\n  implements SlowInstanceCommand\n{\n  async runDataMigration(dataSource: DataSource): Promise<void> {\n    for (const table of TABLES) {\n      const activeCountBefore = await this.getActiveCount(dataSource, table);\n\n      await dataSource.query(\n        `UPDATE \"core\".\"${table}\" SET \"overrides\" = \"standardOverrides\" WHERE \"standardOverrides\" IS NOT NULL AND \"overrides\" IS NULL`,\n      );\n\n      const activeCountAfter = await this.getActiveCount(dataSource, table);\n\n      if (activeCountBefore !== activeCountAfter) {\n        throw new Error(\n          `BackfillMetadataOverrides: \"isActive\" changed on \"core\".\"${table}\" (${activeCountBefore} -> ${activeCountAfter}), aborting.`,\n        );\n      }\n    }\n  }\n\n  public async up(_queryRunner: QueryRunner): Promise<void> {\n    return;\n  }\n\n  public async down(_queryRunner: QueryRunner): Promise<void> {\n    return;\n  }\n\n  private async getActiveCount(\n    dataSource: DataSource,\n    table: (typeof TABLES)[number],\n  ): Promise<number> {","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-slow-1782986476000-backfill-metadata-overrides.ts#L5-L41","documentation":"Thrown by the 2.19 slow instance command that backfills the overrides column from standardOverrides when the count of active rows changes between the before- and after-UPDATE measurements. The migration is designed to be pure data movement (copying standardOverrides into overrides for null overrides rows), so any change in active row count means concurrent activation/deactivation happened during the run, and the migration aborts to avoid an inconsistent result.","triggerScenarios":"Concurrent writes to core.objectMetadata or core.fieldMetadata during the slow instance migration that flip isActive on any row; another upgrade process, a workspace creation, or an object/field delete operation running in parallel with the slow command's runDataMigration.","commonSituations":"Running the slow instance migration while the API server is still serving traffic that creates/deactivates objects or fields; running two upgrade runners at once; long-running data migration overlapped with active workspace provisioning.","solutions":["Run slow instance migrations in a maintenance window with the API server stopped or with workspace mutations paused.","Re-run the slow instance command after the concurrency source is gone; the UPDATE is idempotent for null-overrides rows.","If the count drift is persistent, audit for stale workspace-deactivation jobs or scheduled cleanups that flip isActive.","Confirm only one upgrade runner is active (check for orphaned runners)."],"exampleFix":"// before: slow migration runs while API serves traffic\n// after: stop API, run migration, restart\n// process: yarn workspace twenty-server stop && npx nx run twenty-server:database:migrate:prod && yarn workspace twenty-server start","handlingStrategy":"validation","validationCode":"// Preflight: confirm no concurrent process flips isActive during the slow command.\n// Pause API / workspace provisioning, then assert counts are stable across two reads.\nconst before = await dataSource.query(`SELECT count(*)::int AS c FROM core.\"objectMetadata\" WHERE \"isActive\" = true`);\nawait sleep(2000);\nconst after = await dataSource.query(`SELECT count(*)::int AS c FROM core.\"objectMetadata\" WHERE \"isActive\" = true`);\nif (before[0].c !== after[0].c) {\n  throw new Error('isActive churn detected; run slow migration in a maintenance window');\n}","typeGuard":null,"tryCatchPattern":"// The guard is built into the migration itself. Re-run after quiescing traffic.\ntry {\n  await instanceMigration.runDataMigration(dataSource);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('BackfillMetadataOverrides:')) {\n    // quiesce traffic, then re-run; the UPDATE is idempotent for null-overrides rows\n    await waitForQuietPeriod();\n    throw err;\n  }\n  throw err;\n}","preventionTips":["Run slow instance migrations in a maintenance window with the API server stopped.","Ensure only one upgrade runner is active (kill orphans before starting).","Disable scheduled workspace creation/deactivation during the migration window.","The UPDATE is idempotent for null-overrides rows, so re-running after quiescing is safe."],"tags":["migration","instance-upgrade","slow-command","concurrency","data-backfill"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}