Files
Alex Newman 0b8288bf7c fix(worker): repair orphaned FK parents before migration rebuilds; adoption after db init (#3378) (#3390)
* test(sqlite): pin the #3378 background-init FK abort to the v7/v9 rebuild copies (red)

Pin-down proof on unfixed code: a legacy v4-era DB seeded (with
PRAGMA foreign_keys = OFF, mimicking historical data) with a child row
whose memory_session_id has no sdk_sessions parent aborts the
SessionStore constructor migration chain:

- orphaned observation  -> SQLiteError: FOREIGN KEY constraint failed
  at makeObservationsTextNullable (SessionStore.ts:1167), the
  INSERT INTO observations_new SELECT copy; committed schema_versions
  stop at [4,5,6,7,8], v9 absent.
- orphaned session_summary -> same error at
  removeSessionSummariesUniqueConstraint (SessionStore.ts:1074), the
  INSERT INTO session_summaries_new SELECT copy; committed
  schema_versions stop at [4,5,6], v7 absent.

These two child-table rebuilds run with foreign_keys = ON (set per
connection in connection.ts:45) and, unlike the v21/v33/v34 rebuilds,
never disable it - so each orphaned row makes the copy INSERT violate
the freshly created FK, dbManager.initialize() rejects, and the worker
never reports ready. runOneTimeV12_4_3Cleanup cannot be the aborting
step: it swallows its own errors (CleanupV12_4_3.ts:70-76).

This test asserts the desired post-fix behavior (constructor completes,
orphans survive, stub parents created) and currently fails 4/4 with the
FK error above.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSNZmfi4vSYjTeEWtwqKrW

* fix(worker): repair orphaned FK parents before migration rebuilds; adoption after db init (#3378)

Three root-cause fixes for the background-init half of #3378 (the
recycle-loop half shipped in 13.12.3 as 906ffe37b):

1. FK abort (pinned by the red test in the previous commit): the v7 and
   v9 child-table rebuilds (removeSessionSummariesUniqueConstraint,
   makeObservationsTextNullable) copy rows via INSERT ... SELECT with
   foreign_keys = ON, so one legacy orphaned child row (a
   memory_session_id with no sdk_sessions parent) threw 'FOREIGN KEY
   constraint failed', aborted the constructor migration chain, and the
   worker never reported ready. Orphaned rows are live user data served
   by context injection - the missing side is the parent. New
   repairOrphanedSessionParents() creates a minimal completed stub
   session per orphaned memory_session_id immediately before each copy,
   mirroring SyncApply.ensureSessionForMemoryId
   (INSERT ... ON CONFLICT DO NOTHING). No child rows are deleted.

2. 'errors=[object Object]': adoption.errors
   (Array<{worktree, error}>) was passed as a logger CONTEXT value,
   which logger.ts renders with a template literal. New
   formatAdoptionErrors() serializes it to
   '<worktree>: <error>; ...' at the log site.

3. 'database is locked' at boot: the fire-and-forget adoption kick
   started before dbManager.initialize(), racing the boot migration
   writer for the single WAL writer slot through its own connection.
   The kick now starts after dbManager.initialize() and the one-time
   v12.4.3 cleanup; it stays fire-and-forget, only its start moved.

The ready-flag fail-fast gating in initializeBackground is unchanged;
no retry loops or busy_timeout changes - the lock fix is ordering.

Closes the FK-abort half of #3378.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSNZmfi4vSYjTeEWtwqKrW

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:26:30 -07:00
..