LearnNewsExamplesServices
Frontmatter
id11705
titleCollection.isItem should not classify null as an item
stateClosed
labels
bugaicore
assigneesneo-gpt
createdAtMay 21, 2026, 2:35 AM
updatedAtMay 21, 2026, 2:55 AM
githubUrlhttps://github.com/neomjs/neo/issues/11705
authorneo-gpt
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 21, 2026, 2:55 AM

Collection.isItem should not classify null as an item

neo-gpt
neo-gpt commented on May 21, 2026, 2:35 AM

Context

During the 2026-05-20 Sandman run, the operator reported this failure chain while orphan-node cleanup was running:

TypeError: Cannot read properties of null (reading 'id')
    at Store.getKey (src/data/Store.mjs:554:25)
    at Store.splice (src/collection/Base.mjs:1483:45)
    at Store.splice (ai/graph/Store.mjs:161:30)
    at Store.remove (src/collection/Base.mjs:1396:14)
    at Database.removeNode (ai/graph/Database.mjs:413:18)
    at GraphService.removeNodes (ai/services/memory-core/GraphService.mjs:985-986)

The immediate follow-up question identified a small framework-core guard gap:

isItem(value) {
    // We can not use Neo.isObject() || Neo.isRecord(), since collections can store neo instances too.
    return typeof value === 'object'
}

In JavaScript, typeof null === 'object', so Neo.collection.Base#isItem(null) currently returns true.

The Problem

null is not a valid Collection item, but the helper that routes key-vs-item branches classifies it as one. That is a low-level footgun: any caller that passes null into a collection path can be routed into item-key resolution rather than the key/missing-value path, and subclasses such as Neo.data.Store can then dereference the null item inside getKey().

The Sandman failure is one observed symptom, but the fix belongs in the shared Collection helper because isItem() is the structural classifier used by collection methods and subclasses.

The Architectural Reality

  • src/collection/Base.mjs:1294-1296 currently implements isItem(value) as typeof value === 'object'.
  • The inline comment is correct that Neo.isObject() is too narrow because collections can store Neo instances and records.
  • The missing invariant is only the JavaScript null edge case: a broad object/instance classifier still needs value !== null.
  • This is framework-core behavior (Neo.collection.Base), not Memory Core specific. Sandman merely surfaced the bug path.

The Fix

Update Neo.collection.Base#isItem(value) to exclude null while preserving current support for plain objects, records, and Neo instances:

return value !== null && typeof value === 'object'

Add focused unit coverage proving isItem(null) === false while object/record/instance-like values remain item-like.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
Neo.collection.Base#isItem(value) Operator-reported Sandman stack + src/collection/Base.mjs current helper Return true for object-valued collection items, but false for null. Primitive keys remain non-items; callers can still resolve them through key paths. Existing method comment should be preserved or minimally clarified to mention the null guard. Unit test for null plus existing object behavior.

Acceptance Criteria

  • Collection.isItem(null) returns false.
  • Existing object / Neo-instance-compatible classification remains unchanged for non-null objects.
  • A focused Playwright unit test covers the null edge case.
  • No broader collection refactor or Sandman-specific workaround is introduced.

Out of Scope

  • Changing Store.getKey() semantics beyond any test fixture needed for the guard.
  • Broad cleanup of orphan-node pruning or Sandman graph maintenance.
  • Reclassifying arrays/functions/primitives beyond the existing isItem() contract.

Related

  • Sandman failure surfaced through GraphService.removeNodes() / Database.removeNode() cleanup.
  • Prior key-resolution history: archived #9067 / #9069 around Store.getKey() and collection identity routing.

Origin Session ID: d13c94dd-e721-4e28-ac9e-4d0b3c0f66de

Handoff Retrieval Hints: Collection.isItem null Store.getKey Sandman removeNodes, src/collection/Base.mjs isItem typeof value object, Cannot read properties of null reading id Store.getKey.