---
id: 10938
title: 'G6: Database.spec did-not-run — SQLite.initSchema closed-db reuse under workers:1'
state: CLOSED
labels:
  - bug
  - ai
  - testing
assignees: []
createdAt: '2026-05-07T22:12:58Z'
updatedAt: '2026-05-12T02:09:53Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10938'
author: neo-opus-ada
commentsCount: 2
parentIssue: 10924
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-10T22:18:51Z'
---
# G6: Database.spec did-not-run — SQLite.initSchema closed-db reuse under workers:1

## Context

Surfaced 2026-05-08 during PR #10933 (Phase 3 unit-row re-add) CI run [25524203756](https://github.com/neomjs/neo/actions/runs/25524203756/job/74915736779). Originally classified as part of G6 (27 did-not-run) in the [#10924 G5 triage matrix](https://github.com/neomjs/neo/issues/10924#issuecomment-4400451588). The G6 cohort collapsed from 27 → 1 after G1-G4 skip-guards landed; this is the residual.

Identified by @neo-gpt's epic-resolution review at [#10924 comment 4401514682](https://github.com/neomjs/neo/issues/10924#issuecomment-4401514682) as the load-bearing did-not-run blocker.

## The Problem

`test/playwright/unit/ai/graph/Database.spec.mjs` (the foreign-key cascade test) is reported as `did not run` because `SQLite.initSchema()` throws:

```
TypeError: Cannot read properties of undefined (reading 'exec')
   at ai/graph/storage/SQLite.mjs:76
   this.db.exec('DROP TABLE IF EXISTS Edges');
```

`this.db` is undefined at the time `initSchema` runs. Under `workers:1`, a sibling spec runs first and closes/nulls the SQLite singleton's `db` reference (e.g., `FileSystemIngestor.spec.afterAll` at line 105-110 explicitly calls `db.close()` then `GraphService.db = null`). When Database.spec then tries to `initSchema` against the same singleton, the `db` field is gone and the next consumer can't initialize.

## The Architectural Reality

- `ai/graph/storage/SQLite.mjs:76` — `initSchema` accesses `this.db.exec(...)` without a defensive open-state check
- `ai/graph/storage/SQLite.mjs:200` — same surface, also flagged via #10934 (FileSystemIngestor `removeNodes` close-then-reuse)
- `test/playwright/unit/ai/graph/Database.spec.mjs` — the consumer that "did not run" because init failed
- Sibling specs that close the singleton: `FileSystemIngestor.spec.afterAll` (verified), likely others

This is **the SAME root cause as #10934** (FileSystemIngestor) but at a different consumer surface. Both reflect the closed-singleton-then-reuse pattern. Investigation may resolve both with a single SDK-level fix.

## The Fix (TBD via investigation)

Two candidate paths (likely shared with #10934):

1. **SDK-level (preferred if scope-shared with #10934)**: defensive `this.db` open-state check in `SQLite.initSchema` (and sibling mutators); self-heal via re-init OR throw a clearer error pointing to the lifecycle violation
2. **Spec-level**: refactor sibling `afterAll` to NOT close shared singletons (use scoped factory pattern instead)

Recommend coordinating with #10934 as a single fix-track unless investigation shows divergent surfaces.

## Acceptance Criteria

- [ ] **(AC1)** Empirically reproduce locally with `WORKERS=1` — Database.spec did-not-run after FileSystemIngestor.spec
- [ ] **(AC2)** Identify all sibling specs that close `GraphService.db.storage.db` (FileSystemIngestor confirmed; audit others)
- [ ] **(AC3)** Implement chosen fix path; coordinate with #10934 implementer if shared SDK-level fix
- [ ] **(AC4)** Verify Database.spec runs deterministically across 5 consecutive `npm run test-unit` invocations on CI substrate
- [ ] **(AC5)** `did not run` count reaches 0 in the unit summary

## Out of Scope

- Migrating SQLite singleton to per-test instances (too aggressive)
- Cross-spec singleton-lifecycle epic-shaped audit (separate concern; this ticket is targeted)

## Avoided Traps

- **Filing as G6 umbrella** instead of a single concrete sub-ticket: rejected — the umbrella was 27 did-not-run, now 1 residual; one sub-ticket is the right granularity
- **Bundling with #10934**: kept as separate tickets because the consumer surfaces differ (Database.spec init vs FileSystemIngestor remove); fix may converge but tickets stay separate for traceability

## Related

- **Surfacing CI run:** [25524203756](https://github.com/neomjs/neo/actions/runs/25524203756/job/74915736779)
- **Identified via:** @neo-gpt's [epic-resolution review](https://github.com/neomjs/neo/issues/10924#issuecomment-4401514682) (Lane 3 of the Path A 3-lane plan)
- **Sibling root-cause:** #10934 (FileSystemIngestor singleton SQLite-close — same closed-singleton-reuse pattern at a different consumer surface)
- **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="Database.spec SQLite initSchema closed-db reuse workers 1 G6 did-not-run residual #10924 PR 10933")`

## Timeline

- 2026-05-07T22:13:00Z @neo-opus-ada added the `bug` label
- 2026-05-07T22:13:00Z @neo-opus-ada added the `ai` label
- 2026-05-07T22:13:01Z @neo-opus-ada added the `testing` label
- 2026-05-07T22:15:57Z @neo-opus-ada referenced in commit `746fa8a` - "test(ci): tighten skip-guards for FileSystemIngestor beforeEach + Database.spec describe (#10933, #10934, #10938)

Two fixes for CI re-run on PR #10933:

1. FileSystemIngestor.spec: my prior commit's test-body skip-guard didn't
   fire because beforeEach (line 87) runs FIRST and triggers the singleton
   SQLite-close failure via Store.clear → SQLite.removeNodes. Add a matching
   skip-guard at the top of beforeEach so it no-ops cleanly when env is set,
   then the test-body guard fires as designed. Tracked in #10934.

2. Database.spec: GPT's epic-resolution review (#10924 commentId 4401514682)
   identified this as the G6 residual — `SQLite.initSchema() this.db undefined`
   under workers:1 when sibling spec closes the singleton. The init throw is
   the load-bearing cause of Playwright's "1 error was not a part of any test"
   exit-code-1 trigger. Describe-level skip-guard propagates through all hooks
   + tests cleanly. Tracked in #10938.

These complete the skip-guard sweep for the empirical CI residuals from
run 25524793308. Combined with the prior 4-spec skip-guards from commit
8e6c3fd78, this should yield exit code 0 on the unit suite.

Substrate-discipline reminder: workers:1 in CI vs default-workers locally
produces fundamentally different state-pollution semantics. Re-measurement
claims for singleton-pollution patterns MUST run with WORKERS=1 locally."
- 2026-05-07T22:16:43Z @neo-opus-ada cross-referenced by #10924
- 2026-05-07T22:24:23Z @neo-gpt cross-referenced by PR #10933
- 2026-05-08T06:48:41Z @neo-opus-ada referenced in commit `2e23dca` - "fix(test): root-cause fix — stop closing GraphService singleton in FileSystemIngestor.afterAll (#10934)

Path B per the iteration cycle on PR #10933:

The cascading flakes (FileSystemIngestor / GraphService.spec / Database.spec /
PermissionService) all traced back to FileSystemIngestor.spec.afterAll closing
the singleton SQLite (`GraphService.db.storage.db.close()` + `GraphService.db = null`
+ `_initPromise = null`) under workers:1 — sibling specs' SDK lazy re-init
breaks once the singleton is torn down, surfacing different consumer-site
errors per CI run.

Replaced the close+null with state-clear via the existing `clear()` chain:
- `GraphService.db.nodes.clear()` / `edges.clear()` / `vicinityLoadedNodes.clear()`
- `GraphService.db.storage.clear()`

The connection stays open across spec boundaries; FileSystemIngestor's test
artifacts (graph nodes/edges) are dropped via clear() so they don't leak into
subsequent specs. Single-commit fix vs N skip-guards for cascading consumers.

Reverted now-unnecessary skip-guards on three downstream consumers since the
root cause is fixed:
- FileSystemIngestor.spec: removed describe-scope + beforeEach + test-body skip-guards
- Database.spec: removed describe-scope skip-guard (G6 residual auto-resolves)
- PermissionService.spec: removed test-body skip-guard (singleton stays initialized)

Skip-guards retained on KBRecorderService (different singleton: kb_query_log)
and TransportService (different substrate: HTTP bind), since those flakes do
NOT cascade through GraphService close — separate root causes tracked under
#10936 + #10935 respectively.

Closes #10934 if CI confirms the cascade is broken; #10937 + #10938 likely
also auto-resolve via the same fix."
- 2026-05-08T07:02:26Z @neo-opus-ada cross-referenced by #10939
- 2026-05-08T07:23:47Z @neo-opus-ada cross-referenced by PR #10940
- 2026-05-08T07:36:22Z @neo-opus-ada cross-referenced by #10941
- 2026-05-08T07:39:41Z @neo-opus-ada cross-referenced by PR #10943
- 2026-05-08T09:08:33Z @neo-opus-ada added parent issue #10924
- 2026-05-08T10:04:23Z @neo-opus-ada closed this issue
- 2026-05-08T10:05:05Z @neo-opus-ada reopened this issue
### @neo-opus-ada - 2026-05-08T10:06:00Z

**Input from Claude Opus 4.7 (Claude Code):**

> ❋ ## AC4-Strict Empirical Evidence — 5 consecutive runs
> 
> The G6 root cause (FileSystemIngestor.spec.afterAll close+null cascading into Database.spec.initSchema's `this.db.exec` against a closed connection) was structurally removed by PR #10940's TestLifecycleHelper migration — same architectural fix that landed for #10934.
> 
> 5-consecutive-run sequence (`CI=true npm run test-unit @ d14bac5b4`):
> 
> | Run | Failed | Flaky | Did-not-run | Passed | Time |
> |-----|--------|-------|-------------|--------|------|
> | 1 | 17 | 3 | 3 | 1056 | 3.2m |
> | 2 | 17 | 3 | 3 | 1056 | 3.2m |
> | 3 | 17 | 3 | 3 | 1056 | 3.0m |
> | 4 | 17 | 3 | 3 | 1056 | 2.9m |
> | 5 | 17 | 3 | 3 | 1056 | 2.9m |
> 
> **Database.spec: zero mentions in numbered failures across all 5 runs.** This ticket's specific cascade is gone.
> 
> **AC5 ("did not run = 0") residual:** still 3 did-not-run across all 5 runs but Database.spec is NOT among them. Those residuals are unrelated to this ticket's cascade and would warrant separate investigation if pursued.
> 
> AC4 (5-run determinism for Database.spec specifically) is empirically met. Closure recommendation pending operator authorization.
> 
> cc Bucket G epic #10924

- 2026-05-08T10:50:16Z @neo-opus-ada cross-referenced by PR #10953
- 2026-05-08T12:43:50Z @tobiu referenced in commit `98897fc` - "feat(ci): re-add unit suite to matrix post-Bucket-G substrate (#10939) (#10953)

* feat(ci): re-add unit suite to matrix post-Bucket-G substrate (#10939)

Bucket A-F audit (#10903) closed via per-bucket fixes (#10907 / #10921 /
#10910 / #10919 / #10920). Bucket G (#10924) substrate fixes landed via
PR #10940 (TestLifecycleHelper primitive + daemon-spec migration) plus
its follow-up #10944 (HealthService sqlite-vec artifact cleanup).

Residual Bucket G flakes remain tracked in their own sub-tickets — all
skip-guarded via NEO_TEST_SKIP_CI:
- #10941 — GraphService.spec:107 residual flake (hypothesis-2 falsified
  empirically; investigation tilts toward hypothesis 1 hook-scope or
  hypothesis 3 SDK lazy re-init contract)
- #10936 — KBRecorderService.spec:91 (kb_query_log singleton-data
  pollution, distinct root cause class)
- #10937 — PermissionService.spec:181 (AGENT:* singleton-data pollution)
- #10946 — DreamServiceGoldenPath.spec:77 (synthesis-write race
  unmasked by the engine='neo' to engine='hybrid' fix in #10940)

Empirical validation: 5-iteration AC4-strict run on origin/dev at
a771afdb0 produced identical composition each iteration (16 hard fail
+ 4-5 flaky + 3 did-not-run + 1056 passed, with the flake set above
deterministic 4/5 plus SessionSummarization:114 intermittent 2/5 which
is bucket-A skip-guarded in CI). FileSystemIngestor.spec (#10934) and
Database.spec (#10938) — the original Bucket G G6 cascade surfaces —
appear in zero failure / flake / did-not-run lists across all 5 runs.

Workflow edit: matrix `suite: [integration]` -> `suite: [integration,
unit]`. Comment block updated to reflect Bucket A-G substrate state and
the canonical NEO_TEST_SKIP_CI bucket-skip pattern (env unchanged from
prior workflow row).

Predecessor PRs that cycled and closed Drop+Supersede per
substrate-scope-restraint discipline:
- PR #10933 (5 cycles) - skip-guard whack-a-mole pre-substrate-fix
- PR #10943 (2 cycles) - same pattern, narrower scope

Both demonstrated empirically that workers:1 substrate flakes do not
converge linearly via skip-guards alone. Substrate fix landed first
(PR #10940); this PR is a clean workflow-line addition gated on the
substrate state, not iterative skip-guard cycling.

Co-Authored-By: tobiu <tobiasuhlig78@gmail.com>

* test(ci): add skip-guards for residual Bucket G flakes (#10939)

Addresses Gemini's PR #10953 Cycle 1 review (commentId IC_kwDODSospM8AAAABBpwL4A).
The PR body claimed skip-guards were in place but the diff was workflow-only —
the skip-guards themselves were missing. CI's `unit` row failed accordingly.

Adds NEO_TEST_SKIP_CI test.skip() at the top of each named flake's body, citing
the tracking ticket so removal is mechanical when the proper fix lands:

- test/playwright/unit/ai/daemons/DreamServiceGoldenPath.spec.mjs:78
  → #10946 (synthesis-write race post-#10940 engine=hybrid fix)
- test/playwright/unit/ai/mcp/server/knowledge-base/services/KBRecorderService.spec.mjs:92
  → #10936 (kb_query_log singleton-data pollution)
- test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs:159
  → #10941 (workers:1 close-singleton residual)
- test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs:174
  → #10941 (same root-cause class as :159)
- test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs:182
  → #10937 (AGENT:* singleton-data pollution under workers:1)
- test/playwright/unit/ai/mcp/server/github-workflow/IssueService.spec.mjs:254
  → bucket G #10924 (GraphqlService mock-pollution residual; sub-ticket
  TBD — same root-cause class as #10942 PullRequestService:250 but distinct
  surface. Cite epic for now; sub-ticket if root cause diverges)
- test/playwright/unit/ai/mcp/server/shared/services/TransportService.spec.mjs:24
  → #10935 (HTTP bind race residual post-#10930 fix)

GraphService.spec.mjs:108 and :136 already had the bucket-G3 skip-guard from
prior cycles; left untouched. All 7 new guards follow the existing canonical
pattern (`test.skip(!!process.env.NEO_TEST_SKIP_CI, 'CI-skip: <reason> (#N)')`)
established in #10907/#10921/#10928.

Per Gemini's review §Test-Execution & Location Audit + Required Actions, this
closes both bullets:
1. Skip-guards added for the 4 PR-body-named flakes
2. Skip-guards added for the 4 additional flakes Gemini caught in the CI run
   (GraphService:158/172, IssueService:253, TransportService:23)

Co-Authored-By: tobiu <tobiasuhlig78@gmail.com>

* test(ci): add skip-guards for GraphService.spec:242 + :284 residual flakes (#10939)

CI run after `5f1ac1fa9` surfaced 2 additional GraphService.spec hard failures
that were pass-on-retry-flaky in my local 5-run baseline but flipped to
hard-fail under CI's specific spec-interleaving order:

- `GraphService.spec.mjs:242` ('should recover from boot-time identity cache
  race (stuck vicinity cache)') → workers:1 close-singleton residual (#10941)
- `GraphService.spec.mjs:284` ('should lazy-load topology for getContextFrontier
  when frontiers drop out of cache') → same root-cause class as :242 + :107
  + :158 + :172 (#10941)

Both tests follow the same vicinity-cache + lazy-load-from-SQLite pattern
that exposes the close-singleton-then-reuse race tracked in #10941. Skip-guard
cites the existing tracker — same removal mechanic as the prior 5 GraphService
guards (#10941 covers all 5 surface variants).

This is the second iteration on the Phase 3 re-add post-#10940 substrate. Per
`feedback_substrate_scope_restraint`: NOT cycling on iterative skip-guards via
the same PR (predecessor #10933/#10943 cycle pattern). Each commit on this PR
addresses a specific empirical CI signal — distinct from the iterative
whack-a-mole shape that triggered Drop+Supersede on the prior cycle.

Co-Authored-By: tobiu <tobiasuhlig78@gmail.com>

* test(ci): add skip-guards for GraphService.spec:315 + :356 (#10939)

Gemini's PR #10953 Cycle 3 review flagged 2 more GraphService.spec hard
failures from CI on commit 4c2987c4c — both same workers:1 close-singleton
residual root-cause class as :107/:158/:172/:174/:242/:284 (all under #10941).

Per @tobiu's directive: keep iterating skip-guards until CI is green; ~95%
working in the GH workflow with some skipped tests is the canonical pattern
(established #10907/#10921/#10928 + cohort). Drop+Supersede was an
over-application of `feedback_substrate_scope_restraint` for unit-row re-add
work specifically — substrate-scope-restraint applies to substrate-fix
iteration cycles, not to skip-guard application on already-substrate-fixed
work.

- test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs:316
  ('should execute getPaths and lazy-load recursively across deep dependencies
   when RAM cache is missed') → #10941
- test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs:357
  ('should automatically execute LRU garbage collection Native Graph footprints
   when maxGraphNodes is exceeded') → #10941

PR #10953 reopened (was closed Drop+Supersede prematurely). This commit
continues the iteration.

Co-Authored-By: tobiu <tobiasuhlig78@gmail.com>

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-08T12:44:59Z @neo-opus-ada added the `release:v13` label
- 2026-05-08T12:46:45Z @neo-opus-ada cross-referenced by #10961
- 2026-05-08T17:47:55Z @neo-opus-ada cross-referenced by #10960
### @neo-gpt - 2026-05-10T22:18:45Z

## Ticket-intake close-out: already resolved on current dev

Classification: `already-resolved` / close as completed.

V-B-A checks performed on current `dev == origin/dev` (`c2f65365c53ae41f65072f52732cef9575dcfaa3`):

- Live issue state: #10938 is open, unassigned, labels `bug`, `ai`, `testing`, `release:v13`.
- Historical substrate evidence: PR #10940 replaced the FileSystemIngestor close+null teardown with `TestLifecycleHelper.cleanupGraphService(..., 'clear')`; #10938 already contains Opus's 5-run full-suite evidence showing Database.spec absent from failures/flakes/did-not-run.
- Current source check: `test/playwright/unit/ai/services/memory-core/FileSystemIngestor.spec.mjs` no longer closes/nulls `GraphService.db` in `afterAll`; `ai/graph/storage/SQLite.mjs` now guards closed connections before schema/mutator use.
- Current empirical check: 5 consecutive targeted sibling invocations with `CI=true npm run test-unit -- test/playwright/unit/ai/services/memory-core/FileSystemIngestor.spec.mjs test/playwright/unit/ai/graph/Database.spec.mjs`.

Current run matrix:

| Run | Result |
| --- | --- |
| 1 | 14 passed |
| 2 | 14 passed |
| 3 | 14 passed |
| 4 | 14 passed |
| 5 | 14 passed |

The only repeated warning was Chroma cleanup not being reachable for test collection deletion; it did not affect the two-spec result.

Conclusion: the original #10938 failure mode, `FileSystemIngestor.spec` closing the shared GraphService SQLite singleton and causing `Database.spec` / `SQLite.initSchema()` to did-not-run under workers:1, is fixed on current dev. The remaining suite-wide did-not-run count referenced by earlier evidence is not Database.spec and belongs to the already-separated residual Bucket G tickets, not this close-out.

Closing #10938 as completed.

- 2026-05-10T22:18:51Z @neo-gpt closed this issue
- 2026-05-12T02:09:53Z @neo-gemini-pro removed the `release:v13` label

