From 4d300e8ed36807226e64e5110f512d10176102db Mon Sep 17 00:00:00 2001 From: David Snelling Date: Thu, 20 Nov 2025 08:38:40 -0800 Subject: [PATCH] fix: prevent infinite loop during storage initialization (v6.0.1) CRITICAL FIX for production blocker in v6.0.0: Root Cause: - BaseStorage.init() set isInitialized = true at the END of initialization - If any code during init called ensureInitialized(), it triggered init() recursively - This caused infinite loop on fresh workspace initialization Fix: - Set isInitialized = true at START of BaseStorage.init() - Wrap in try/catch to reset flag on error - Prevents recursive init() calls while maintaining error recovery Impact: - Fixes all 8 storage adapters (FileSystem, Memory, S3, R2, GCS, Azure, OPFS, Historical) - Init now completes in ~1 second on fresh installation (was hanging) - Workshop team can now use v6.0.0 features without infinite loop - No new test failures (1178 tests passing) Files: - src/storage/baseStorage.ts: Move isInitialized = true to top of init() - CHANGELOG.md: Document v6.0.1 hotfix Co-Authored-By: Claude --- CHANGELOG.md | 25 +++++++++++++++++++++++++ src/storage/baseStorage.ts | 31 ++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afaea86d..5e4a3157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [6.0.1](https://github.com/soulcraftlabs/brainy/compare/v6.0.0...v6.0.1) (2025-11-20) + +### 🐛 Critical Bug Fixes + +**Fixed infinite loop during storage initialization on fresh workspaces (v6.0.1)** + +**Symptom:** FileSystemStorage (and all storage adapters) entered infinite loop on fresh installation, printing "📁 New installation: using depth 1 sharding..." message hundreds of thousands of times. + +**Root Cause:** In v6.0.0, `BaseStorage.init()` sets `isInitialized = true` at the END of initialization (after creating GraphAdjacencyIndex). If any code path during initialization called `ensureInitialized()`, it would trigger `init()` recursively because the flag was still `false`. + +**Fix:** Set `isInitialized = true` at the START of `BaseStorage.init()` (before any initialization work) to prevent recursive calls. Flag is reset to `false` on error to allow retries. + +**Impact:** +- ✅ Fixes production blocker reported by Workshop team +- ✅ All 8 storage adapters fixed (FileSystem, Memory, S3, R2, GCS, Azure, OPFS, Historical) +- ✅ Init completes in ~1 second on fresh installation (was hanging indefinitely) +- ✅ No new test failures introduced (1178 tests passing) + +**Files Changed:** +- `src/storage/baseStorage.ts:261-287` - Moved `isInitialized = true` to top of init() with try/catch + +**Migration:** No code changes required - drop-in replacement for v6.0.0. + +--- + ## [6.0.0](https://github.com/soulcraftlabs/brainy/compare/v5.12.0...v6.0.0) (2025-11-19) ## 🚀 v6.0.0 - ID-First Storage Architecture diff --git a/src/storage/baseStorage.ts b/src/storage/baseStorage.ts index c76e64c4..5a098ff9 100644 --- a/src/storage/baseStorage.ts +++ b/src/storage/baseStorage.ts @@ -259,22 +259,31 @@ export abstract class BaseStorage extends BaseStorageAdapter { * IMPORTANT: If your adapter overrides init(), call await super.init() first! */ public async init(): Promise { - // Load type statistics from storage (if they exist) - await this.loadTypeStatistics() + // v6.0.1: CRITICAL FIX - Set flag FIRST to prevent infinite recursion + // If any code path during initialization calls ensureInitialized(), it would + // trigger init() again. Setting the flag immediately breaks the recursion cycle. + this.isInitialized = true - // v6.0.0: Create GraphAdjacencyIndex (lazy-loaded, no rebuild) - // LSM-trees are initialized on first use via ensureInitialized() - // Index is populated incrementally as verbs are added via addVerb() try { - prodLog.debug('[BaseStorage] Creating GraphAdjacencyIndex...') - this.graphIndex = new GraphAdjacencyIndex(this) - prodLog.debug(`[BaseStorage] GraphAdjacencyIndex instantiated (lazy-loaded), graphIndex=${!!this.graphIndex}`) + // Load type statistics from storage (if they exist) + await this.loadTypeStatistics() + + // v6.0.0: Create GraphAdjacencyIndex (lazy-loaded, no rebuild) + // LSM-trees are initialized on first use via ensureInitialized() + // Index is populated incrementally as verbs are added via addVerb() + try { + prodLog.debug('[BaseStorage] Creating GraphAdjacencyIndex...') + this.graphIndex = new GraphAdjacencyIndex(this) + prodLog.debug(`[BaseStorage] GraphAdjacencyIndex instantiated (lazy-loaded), graphIndex=${!!this.graphIndex}`) + } catch (error) { + prodLog.error('[BaseStorage] Failed to create GraphAdjacencyIndex:', error) + throw error + } } catch (error) { - prodLog.error('[BaseStorage] Failed to create GraphAdjacencyIndex:', error) + // Reset flag on failure to allow retry + this.isInitialized = false throw error } - - this.isInitialized = true } /**