Skip to content

fix: detach user_memories before DELETE FROM metabots to avoid FOREIGN KEY constraint failed - #6

Open
WuFenG-Hub wants to merge 1 commit into
metaid-developers:mainfrom
WuFenG-Hub:fix/delete-metabot-fk
Open

fix: detach user_memories before DELETE FROM metabots to avoid FOREIGN KEY constraint failed#6
WuFenG-Hub wants to merge 1 commit into
metaid-developers:mainfrom
WuFenG-Hub:fix/delete-metabot-fk

Conversation

@WuFenG-Hub

@WuFenG-Hub WuFenG-Hub commented Aug 9, 2026

Copy link
Copy Markdown

Problem

Deleting any bot that has stored memories fails with FOREIGN KEY constraint failed (19).

Root cause chain:

  1. user_memories.metabot_id INTEGER REFERENCES metabots(id) has no ON DELETE clause (SQLite default is NO ACTION — the parent row cannot be deleted while child rows reference it). Defined at src/main/sqliteStore.ts:351 (CREATE TABLE) and src/main/sqliteStore.ts:1456 / src/main/coworkStore.ts:1153 (ALTER TABLE migrations).
  2. Every database connection forces PRAGMA foreign_keys = ON (src/main/nativeSqliteDatabase.ts:58), so the constraint is actually enforced.
  3. deleteMetabot (src/main/metabotStore.ts) only ran UPDATE metabots SET boss_id = NULL and DELETE FROM metabots WHERE id = ?, never releasing user_memories.metabot_id. Its comment says it deliberately does not cascade user_memories to preserve history — which directly conflicts with the NO ACTION FK, so the delete is rejected by the database.

Reproduction: with PRAGMA foreign_keys=ON, DELETE FROM metabots WHERE id = <bot-with-memories> fails; clearing the memory references first makes the delete succeed. cowork_sessions and metabot_memory_policies are ON DELETE CASCADE and are not blockers.

Fix

In deleteMetabot, before DELETE FROM metabots, run:

UPDATE user_memories SET metabot_id = NULL WHERE metabot_id = ?

This detaches memory rows from the deleted bot instead of cascading them — preserving the original intent (experience data survives the bot itself) while satisfying the enforced FK. boss_id cleanup is unchanged.

Chosen as an application-layer fix to avoid a high-cost table rebuild migration; a schema-level ON DELETE SET NULL could be evaluated separately.

Verification

  • New regression tests (tests/metabotDeleteWithMemories.test.mjs, node --test .mjs pattern):
    • deleteMetabot on a bot with 2 attached memories succeeds under foreign_keys=ON; memory rows survive with metabot_id set to NULL; bot row is gone.
    • Raw DELETE FROM metabots with an attached memory is rejected (FOREIGN KEY constraint failed) — proving the constraint is enforced and the fix is required.
    • Deleting an unknown id is a no-op returning false.
    • All pass: node --test tests/metabotDeleteWithMemories.test.mjs → 3 pass / 0 fail.
  • TypeScript compile: npx -p typescript@5.9.3 tsc --project electron-tsconfig.json — error set identical before/after this change (zero new errors; remaining errors are pre-existing, in unrelated files).
  • No real data touched: verification used temporary databases under os.tmpdir() only.
  • Related existing suites (metabotTwinType, metabotSettings, memoryScopedCrud, memoryScopedRecall, metabotAttribution, metabotFallbackLlm, metabotWalletService): 27 pass; 2 pre-existing failures in metabotAccountService.test.mjs (wrong require path dist-electron/services/... instead of dist-electron/main/services/...; reproduced without this change).

Update (2026-08-13)

The fix is consolidated and extended on top of the latest main (0.4.6):

  • metaid_knowledge_entries cleanup: this SDK-managed table also references metabots(id) with NO ACTION, and its metabot_id column is NOT NULL — so a bot with knowledge rows hit the same failure. deleteMetabot now deletes the bot's knowledge rows (best-effort, guarded for installs where the SDK table is absent).
  • Frontend error surfacing: a failed delete previously left the confirm modal open with the error written to actionError behind the overlay, so it looked like the delete did nothing. handleDeleteConfirm now closes the modal and shows the real error via toast (both for failed IPC results and thrown errors), preserving the existing edit-view fallback.
  • Tests: the regression suite now also covers the metaid_knowledge_entries cleanup under foreign_keys=ON (4 tests, all pass).

…urface delete errors

deleteMetabot deleted the metabots row while user_memories.metabot_id still
referenced it. The column is a bare REFERENCES (NO ACTION) and PRAGMA
foreign_keys=ON is forced on every connection, so the DELETE was rejected with
FOREIGN KEY constraint failed (19) for any bot that had memories (or
metaid_knowledge_entries rows, whose metabot_id is NOT NULL). The renderer kept
the delete modal open with the error hidden behind it, so a failed delete
appeared to do nothing.

- Null user_memories.metabot_id before the DELETE so memory rows survive the bot
  as history (matching the original no-cascade intent).
- Delete the bot's metaid_knowledge_entries rows (metabot_id is NOT NULL;
  best-effort for the SDK-managed table).
- Surface delete failures via toast instead of hiding them behind the modal.
- Add regression tests covering deletion with attached memories under
  foreign_keys=ON (rows preserved with metabot_id nulled), the raw DELETE
  rejection, the knowledge-entries cleanup, and the missing-id no-op.
@WuFenG-Hub
WuFenG-Hub force-pushed the fix/delete-metabot-fk branch from 75e0540 to b8f9c63 Compare August 13, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant