New request
#20666: Restart loop after first workspace creation on a fresh self-host install 2.4.0
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Short Description / Error Message:
Restart loop after first workspace creation on a fresh self-host install (Some workspace(s) have not completed the last workspace command for 2.3.0)
Hello from the Cloudron Team 👋🏻
While packaging Twenty 2.4.0 for Cloudron we ran into a problem that I am fairly sure also affects any plain self-host setup. The first restart after the first workspace is created puts the server into a restart loop. The migrate step on startup throws:
Instance commands failed: Unable to run instance commands. Some workspace(s) have not completed the last workspace command for 2.3.0 ("2.3.0_DeleteGaugeWidgetsCommand_1798000000000").
Please ensure all workspaces are upgraded to at least the previous version before running migrations.
Use --force to bypass this check (not recommended).
The workspace was just created on 2.4.0 from an empty database, so there is nothing to migrate at all. The check is firing on a healthy install.
Steps to reproduce
- Empty database, no
core."workspace"rows, nocore."upgradeMigration"rows. yarn database:init:prod. Completes fine.- Start the server, sign up, first workspace gets created.
- Restart the server, which re-runs
yarn database:migrate:prodon startup. - The migrate step throws the error above and the process exits. If the container has auto-restart you now have a restart loop.
Adding --force to database:migrate:prod makes it go away, but the check is wrong, not catching something real.
What is going on
The safety check is here:
It takes the last entry of TWENTY_PREVIOUS_VERSIONS:
asks the registry for the last workspace command of that version (2.3.0_DeleteGaugeWidgetsCommand_1798000000000 in our case), and then asks areAllWorkspacesAtCommand({ commandName, workspaceIds }) whether every active workspace has a core."upgradeMigration" row with exactly that name and status='completed'. If not, throw.
On the workspace creation side:
activateAndInitializeUpgradeState() writes only one row via markAsWorkspaceInitial. The cursor it uses comes from getInitialCursorForNewWorkspace:
For a 2.4.0 fresh install this returns the last workspace command of the 2.4.0 segment (2.4.0_MigrateToBillingV2Command_1797000001000). The 2.3.0 segment's last workspace command is never written. The cursor based runner (getPendingWorkspaceCommands) is fine with that, it treats anything before the cursor as done. But areAllWorkspacesAtCommand is just a row existence query and does not know about cursors.
So:
- the workspace's only init row is the last 2.4.0 workspace command
- the safety check looks for the last 2.3.0 workspace command
- the row does not exist
- check throws, startup fails, restart loop
What the DB actually looks like
core."upgradeMigration" for the affected workspace, ordered by createdAt desc:
name | status | is_instance | isInitial
----------------------------------------------------------------+-----------+-------------+-----------
2.4.0_MigrateToBillingV2Command_1797000001000 | completed | f | t
2.4.0_AddApplicationIdToPublicDomainFastInstanceCommand_... | completed | t | f
... | | |
2.3.0_RemoveUserDefaultAvatarUrlFastInstanceCommand_... | completed | t | f
...
All instance commands are there. The only workspace-scoped row is the single isInitial=true cursor at 2.4.0_MigrateToBillingV2Command. No row for 2.3.0_DeleteGaugeWidgetsCommand_1798000000000, which is exactly what the check wants.
This is still present in 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2
I checked the relevant files in every 2.4.x and 2.5.x tag, hoping a newer release would fix it. It does not.
run-instance-commands.command.tshas the same content in all six tags. The safety check is unchanged.upgrade-sequence-reader.service.tshas the same content in all six tags.getInitialCursorForNewWorkspacestill seeds exactly one row.workspace.service.tswas touched in 2.5.0, but the change is aroundenqueueSdkClientGenerationForWorkspace.activateAndInitializeUpgradeStateis the same.upgrade-migration.service.tswas touched in 2.5.0 and 2.5.1, but the changes are a newgetInferredVersionhelper, batching saves in chunks of 1000, and a raw SQL rewrite ofgetWorkspaceLastAttemptedCommandName.markAsWorkspaceInitialandareAllWorkspacesAtCommandbehave the same.
Also, TWENTY_PREVIOUS_VERSIONS simply grows each release, so the same trap re-appears one version later. A workspace created fresh on 2.5.0 will fail on first restart against the missing 2.4.0_<lastWorkspaceCommand> row. A 2.5.1 fresh workspace fails against 2.5.0_<lastWorkspaceCommand>. And so on.
Suggested fix
Two options, either is fine:
- In
activateAndInitializeUpgradeState, also write an initial row for the previous version's last workspace command, when one exists. That makes the existing check accurate. - In
checkWorkspaceVersionSafety, stop looking for an exact row. Use the workspace's latest cursor (you already havegetWorkspaceLastAttemptedCommandName) and accept anything at or after the previous version's last workspace command in the upgrade sequence. That matches howgetPendingWorkspaceCommandsalready reasons.
Option 2 is more general.
Workaround
For anyone hitting this in the meantime: add --force to database:migrate:prod. The database:init:prod script in package.json already does this (yarn database:migrate:prod --force --include-slow), so it is not a new flag and not unsafe in this scenario, but it should not be needed on every restart of a healthy server.
Thanks for the great work on Twenty.
Startup instance command check incorrectly requires all workspaces to complete a 2.3 command even on fresh installs with no prior migration state.
- packages/twenty-server/src/database/typeorm/core/migrations/common
- packages/twenty-server/src/engine/workspace-manager/workspace-migration
- packages/twenty-server/src/engine/core-modules/upgrade/upgrade.service.ts
- packages/twenty-server/src/engine/workspace-manager/commands