Context
Surfaced 2026-05-08 during PR #10933 (Phase 3 unit-row re-add) CI run 25524203756. Test test/playwright/unit/ai/mcp/server/memory-core/services/FileSystemIngestor.spec.mjs:122 flakes with:
TypeError: The database connection is not open
at SQLite.removeNodes (ai/graph/storage/SQLite.mjs:200)
at Database.apply (ai/graph/Database.mjs:387)
at Store.fire (src/core/Observable.mjs:255)
at Store.splice (src/collection/Base.mjs:1608)
at Store.clear (src/collection/Base.mjs:580)
Pass-on-retry, so doesn't break exit code by itself, but contributes to the flake count + cumulative test instability.
The Problem
FileSystemIngestor.spec.afterAll (line ~104-110) closes the singleton GraphService.db.storage.db and nulls GraphService.db. Under CI's workers:1 config (test/playwright/playwright.config.unit.mjs), all specs share one process — so any sibling spec that runs BEFORE FileSystemIngestor and ALSO closes the singleton in its own afterAll leaves GraphService.db in a zombie state. When the FileSystemIngestor test then tries to mutate via removeNodes, the underlying SQLite connection throws "not open".
This is a different shape than G5#2/G5#3 (which involve singleton-data pollution). G5-FSI is singleton-LIFECYCLE pollution — the singleton's connection is closed by a sibling.
The Architectural Reality
test/playwright/unit/ai/mcp/server/memory-core/services/FileSystemIngestor.spec.mjs:104-110 — afterAll closes GraphService.db.storage.db
ai/mcp/server/memory-core/services/GraphService.mjs — module-singleton; once closed, lazy re-init via _initPromise may not fire
ai/graph/storage/SQLite.mjs:200 removeNodes — naked this.db.prepare(...) with no defensive check on closed-connection state
The Fix (TBD via investigation)
Two candidate paths:
- Spec-level: refactor
afterAll to NOT close shared singletons. Use beforeAll to ensure-init OR move to test.describe.configure({mode: 'serial'}) to bound the lifecycle.
- SDK-level: defensive
this.db open-state check in SQLite.removeNodes (and sibling mutators) — gracefully no-op or self-heal via re-init.
Investigation needed before locking the prescription.
Acceptance Criteria
Out of Scope
- Cross-spec singleton-lifecycle audit (separate epic-shaped concern; this ticket is the targeted fix for FileSystemIngestor specifically)
- Migrating GraphService to per-test instances (too aggressive)
Avoided Traps
- Skip-guard via NEO_TEST_SKIP_CI: deferred to here as the immediate ship-the-PR move on PR #10933, but NOT the proper fix. Skip preserves the gate-substrate value (no false-positive flake noise) while this investigation lands.
- Increasing
retries: 2 → 5: doesn't address the root cause; just papers over.
- Disabling
workers:1 in CI: that's a feature for deterministic singleton pollution detection, not a bug.
Related
- Surfacing CI run: 25524203756 — PR #10933 unit suite (4 flakes + 999 passing)
- Sibling state-pollution patterns: G5#2 (KBRecorderService — singleton-DATA pollution) + G5#3 (PermissionService — singleton-DATA pollution) per #10924 G5 triage
- Substrate config:
test/playwright/playwright.config.unit.mjs workers: 1 in CI
Origin Session ID: 7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571
Retrieval Hint: query_raw_memories(query="FileSystemIngestor singleton SQLite close GraphService workers 1 flake PR 10933 CI run 25524203756")
Context
Surfaced 2026-05-08 during PR #10933 (Phase 3 unit-row re-add) CI run 25524203756. Test
test/playwright/unit/ai/mcp/server/memory-core/services/FileSystemIngestor.spec.mjs:122flakes with:Pass-on-retry, so doesn't break exit code by itself, but contributes to the flake count + cumulative test instability.
The Problem
FileSystemIngestor.spec.afterAll(line ~104-110) closes the singletonGraphService.db.storage.dband nullsGraphService.db. Under CI'sworkers:1config (test/playwright/playwright.config.unit.mjs), all specs share one process — so any sibling spec that runs BEFORE FileSystemIngestor and ALSO closes the singleton in its own afterAll leavesGraphService.dbin a zombie state. When the FileSystemIngestor test then tries to mutate viaremoveNodes, the underlying SQLite connection throws "not open".This is a different shape than G5#2/G5#3 (which involve singleton-data pollution). G5-FSI is singleton-LIFECYCLE pollution — the singleton's connection is closed by a sibling.
The Architectural Reality
test/playwright/unit/ai/mcp/server/memory-core/services/FileSystemIngestor.spec.mjs:104-110— afterAll closesGraphService.db.storage.dbai/mcp/server/memory-core/services/GraphService.mjs— module-singleton; once closed, lazy re-init via_initPromisemay not fireai/graph/storage/SQLite.mjs:200removeNodes— nakedthis.db.prepare(...)with no defensive check on closed-connection stateThe Fix (TBD via investigation)
Two candidate paths:
afterAllto NOT close shared singletons. UsebeforeAllto ensure-init OR move totest.describe.configure({mode: 'serial'})to bound the lifecycle.this.dbopen-state check inSQLite.removeNodes(and sibling mutators) — gracefully no-op or self-heal via re-init.Investigation needed before locking the prescription.
Acceptance Criteria
workers:1(matches CI substrate)npm run test-unitinvocations on CI substrateOut of Scope
Avoided Traps
retries: 2 → 5: doesn't address the root cause; just papers over.workers:1in CI: that's a feature for deterministic singleton pollution detection, not a bug.Related
test/playwright/playwright.config.unit.mjsworkers: 1in CIOrigin Session ID:
7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571Retrieval Hint:
query_raw_memories(query="FileSystemIngestor singleton SQLite close GraphService workers 1 flake PR 10933 CI run 25524203756")