fix(migration): prevent UNIQUE constraint crash in migration 103 when user has permissions on both dup and keeper channel#3805
Conversation
…tion 103 When a user already has a `channel_database_permissions` row on both the keeper and the duplicate channel, the `UPDATE channel_database_permissions SET channel_database_id = keep WHERE channel_database_id = dup` violated the UNIQUE(user_id, channel_database_id) constraint, crashing migration 103 and making the app unbootable (issue #3804). Fix: add a DELETE that removes the dup-side row for any user who already holds the keep-side row before the UPDATE, for all three backends (SQLite, PostgreSQL, MySQL). The MySQL version wraps the subquery in a derived table to avoid MySQL's Error 1093 restriction on self-referencing DELETE. Also add a test case seeding the exact collision scenario to prevent regressions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNiMxHa13x5DpyjGqZNtSP
|
Claude finished @Yeraze's task —— View job PR Review: fix(migration): prevent UNIQUE constraint crash in migration 103Summary: This is a targeted, well-scoped fix for a real crash in migration 103 ( Code Quality ✅The fix is clean and idiomatic for each database backend:
The comments clearly explain why the DELETE is needed before the UPDATE, which is important for future maintainers. Correctness ✅The fix is logically sound:
This preserves the invariant that users who had access to either duplicate retain access to the canonical keeper. No permission data is lost — users on Test Coverage ✅The new test (
One minor observation: the collision test creates a new in-memory DB ( Potential IssuesMinor: The test at line 138 uses No security concerns. The DELETE uses parameterized queries consistently across all three backends. No raw string concatenation or user-controlled input. No performance concerns. The DELETE sub-select runs once per VerdictLGTM. The fix is minimal, correct, well-tested for all three database backends, and properly handles the edge case. The MySQL derived-table workaround is the right approach. The PR description also helpfully includes a manual workaround for users already stuck. |
Hotfix for #3804: migration 103 UNIQUE-constraint startup crash when upgrading to 4.12.0 with MQTT channel permissions (fix merged in #3805). Bumps all five version files and adds the [4.12.1] CHANGELOG entry. Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Fixes #3804 — migration 103 (
consolidate_mqtt_channels) crashes withSqliteError: UNIQUE constraint failed: channel_database_permissions.user_id, channel_database_permissions.channel_database_idwhen upgrading to 4.12.0 if a user holds permissions on both the duplicate and the keeperchannel_databaserow.Root Cause
Part A of migration 103 merges duplicate
channel_databaserows and then reassigns permissions:If a user already has a permission row on
keep, this UPDATE creates a second(user_id, channel_database_id=keep)row, violating the unique constraint. The existing test only seeded a user with permissions on the dup — not on both — so the collision case was never caught.Changes
src/server/migrations/103_consolidate_mqtt_channels.ts— Before the UPDATE, add a DELETE that removes thedup-side row for any user who already holds a row onkeep. Applied to all three backends:user_id"userId"/"channelDatabaseId"column namesAS tmp) to avoid MySQL Error 1093 (cannot reference the target table of DELETE in a sub-select)src/server/migrations/103_consolidate_mqtt_channels.test.ts— New test case seeds user 99 with permissions on both the dup (id=2) and the keeper (id=1), then asserts the migration completes without throwing and the user retains exactly one permission row on the keeper.Test Results
All 9 tests pass (8 original + 1 new collision test).
Workaround for Existing Broken Installs
Users who are already unable to start after upgrading to 4.12.0 can apply the same patch manually to
/opt/meshmonitor/dist/server/migrations/103_consolidate_mqtt_channels.js, then runsystemctl start meshmonitor.Generated by Claude Code