.maestro/playbooks/2026-03-29-Issues-Triage-3-29-2026/Phase-05-Project-Scoping-And-Session-Integrity.md
This phase fixes two related data integrity problems: (1) project identity uses basename(cwd) which causes memory collisions between unrelated projects sharing a folder name (5 issues), and (2) the async observation pipeline has race conditions causing early finalization, duplicate observations, and unbounded pending queue growth (6 issues). Both undermine user trust in the memory system — if the wrong project's memories appear, or observations silently disappear, the core product promise is broken.
Upgrade project identity from basename(cwd) to parent/basename format throughout the codebase. The fix already exists partially in src/shared/paths.ts (getCurrentProjectName() returns parent/repo format) but src/utils/project-name.ts (getProjectName()) still uses bare basename. To fix:
src/utils/project-name.ts to understand getProjectName(cwd) — it returns just path.basename(cwd)src/shared/paths.ts for getCurrentProjectName() — it returns basename(dirname(gitRoot))/basename(gitRoot) (collision-resistant)getProjectName() using grep -r "getProjectName" to find every usagegetCurrentProjectName() to understand the parallel usagegetProjectName(cwd) to return the parent/basename format, matching what getCurrentProjectName() already does. This is the single function that all hooks and services should usedrive-C on Windows), home directory (return home/<basename>), paths with only one component (return root/<basename>)getProjectContext() in the same file to use the new format for both primary and parent project namesChromaSync.ts yet — that requires a data migration (handled separately below) Add a data migration for existing projects using the old basename-only format. When users upgrade, their existing observations are stored under the old project name (e.g., myapp) but new observations will use the new format (e.g., work/myapp). To fix:
src/services/sqlite/SessionStore.ts to understand the project column in the sessions and observations tablessrc/services/sync/ChromaSync.ts for how project names are used in collection names (cm__<project>) and metadata filtersmigrateProjectNames() in the database layer that:
/ separator), attempts to resolve the full path by checking if a matching git repo exists in common locationslegacy/ (e.g., legacy/myapp) to avoid collisionsproject column in both sessions and observations tables within a transactionprojectNameMigrationComplete: true)Fix early session finalization. Sessions can be finalized (summary generated, session marked complete) while observation messages are still in the pending queue, causing data loss. To fix:
src/services/worker-service.ts for the session finalization trigger — search for "finalize", "summary", or "SessionEnd"src/services/sqlite/PendingMessageStore.ts for the claimNextMessage() / confirmProcessed() patternsrc/services/worker/agents/ResponseProcessor.ts for how observations are stored after processinghasPendingMessages(contentSessionId: string): boolean method to PendingMessageStore that checks SELECT COUNT(*) FROM pending_messages WHERE content_session_id = ? AND status IN ('pending', 'processing')while (await pendingStore.hasPendingMessages(sessionId)) { await sleep(500); } with a maximum wait of 30 seconds, then force-finalize with a warning"Session finalized with ${count} pending messages remaining — some observations may be lost" Fix duplicate observation storage. The content-hash deduplication in SessionStore.storeObservation() uses a 30-second window, but race conditions in concurrent message processing can produce duplicate observations with identical content hashes outside this window. To fix:
src/services/sqlite/SessionStore.ts storeObservation() (around line 1505) and storeObservations() (around line 1631) for the deduplication logicSELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? AND memory_session_id = ? (30-second window)SELECT id FROM observations WHERE content_hash = ? AND memory_session_id = ?(memory_session_id, content_hash) to enforce this at the database level. Use CREATE UNIQUE INDEX IF NOT EXISTS idx_obs_session_hash ON observations(memory_session_id, content_hash) — add this as a migrationUNIQUE constraint failed errors in storeObservation() and return the existing observation ID instead of throwingFix unbounded pending queue growth. When the worker is overloaded or AI processing fails repeatedly, the pending_messages table grows without bound, consuming disk and slowing queries. To fix:
src/services/sqlite/PendingMessageStore.ts for queue managementsrc/services/worker-service.ts processPendingQueues() (around line 811) for the recovery logicgetQueueSize(): number method that returns total pending message count, and expose it on the /api/health endpoint as pendingQueueSize for monitoringWrite tests for project scoping and session integrity:
getProjectName(): verify parent/basename format for normal paths, drive roots, home directories, single-component pathsRun build and verify:
npm run build-and-sync