LearnNewsExamplesServices
Frontmatter
id10184
titleIdentity binding stays null post-#10182 — enable debug logging + diagnose
stateClosed
labels
bugdeveloper-experienceaiarchitecture
assigneesneo-gemini-pro
createdAtApr 22, 2026, 6:15 PM
updatedAtJun 7, 2026, 7:20 PM
githubUrlhttps://github.com/neomjs/neo/issues/10184
authorneo-opus-ada
commentsCount0
parentIssue10139
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[x] 10186 MCP concurrency audit + single-writer enforcement
closedAtApr 22, 2026, 7:05 PM

Identity binding stays null post-#10182 — enable debug logging + diagnose

Closed v13.0.0/archive-v13-0-0-chunk-5 bugdeveloper-experienceaiarchitecture
neo-opus-ada
neo-opus-ada commented on Apr 22, 2026, 6:15 PM

Identity binding stays null post-#10182 — enable debug logging + diagnose

Context

#10182 merged the self-heal hook from #10181's proposed fix path (a): when stdioIdentity.agentIdentityNodeId is null at dispatch time, re-attempt bindAgentIdentity once. The patch is live in dev (commit fda5dd4be as (#10181) (#10182)). Claude Desktop + Antigravity were git pulled and restarted. Binding remains null.

Empirical state captured 2026-04-22T16:07Z, 5 minutes after restart on commit fda5dd4be:

Check Result
Main checkout git head includes self-heal fda5dd4be
Running MCP process (pid 22656, started 18:07:12 CEST) Post-merge; patch is in the file it loaded
grep "Identity self-healed" in Server.mjs ✓ present — code is there
healthcheckmailboxPreview null (binding null)
list_messages Errors "Cannot list messages: no agent identity context bound"
get_node({id: '@neo-opus-ada'}) via MCP Succeeds — returns the full AgentIdentity node
Direct SQLite SELECT id FROM Nodes WHERE id='@neo-opus-ada' Row present (same graph)
NEO_AGENT_IDENTITY=neo-opus-ada in MCP process env Confirmed via ps -E

So the MCP server has the patch, the graph has the node, the env var is set, yet bindAgentIdentity keeps returning null. get_node invoked from the MCP tool surface hits the exact same GraphService.getNode call chain and returns the node fine. Something about the path bindAgentIdentity → await GraphService.ready() → getNode behaves differently from toolDispatch → GraphService.getNode, but we can't tell what because the diagnostic logger is silent.

The Problem

The diagnostic blocker: ai/mcp/server/memory-core/logger.mjs gates every logger.info/warn/error/debug call behind aiConfig.debug:

const createLogMethod = (level) => {
    return (...args) => {
        if (aiConfig.debug) {
            console.error(`[${level.toUpperCase()}]`, ...args);
        }
    };
};

config.mjs line 56 hardcodes debug: false with no environment override. Every trace in the binding path fires to /dev/null:

  • logger.info("[neo-memory-core MCP] Identity: ${userId} via ${source} — ${bound}") (Server.mjs logIdentityStatus)
  • logger.warn("[neo-memory-core MCP] AgentIdentity graph lookup failed...") (bindAgentIdentity catch)
  • logger.info("[neo-memory-core MCP] Identity self-healed: ...") (my #10182 patch)
  • logger.warn("[neo-memory-core MCP] Identity self-heal attempt failed...") (my #10182 patch)

Claude Desktop redirects MCP stderr to ~/Library/Logs/Claude/mcp-server-neo-mjs-memory-core.log, but those logger.* calls never fire their underlying console.error. The log file only captures MCP SDK JSON-RPC frames. Every observable diagnostic signal for this class of failure is dark.

Hypotheses for the binding null (any of which could be the root cause; none provable without logs):

  1. vicinityLoadedNodes stuck-cache. In ai/graph/Database.mjs getAdjacentNodes (lines 252-283), me.vicinityLoadedNodes.add(nodeId) fires regardless of whether vicinity.nodes.length > 0. If the boot-time call for @neo-opus-ada returned empty (for whatever reason — WAL checkpoint state, race, etc.), subsequent lookups skip SQLite. But this contradicts the observation that get_node works live — unless the cache gets invalidated somehow between boot and first tool call.

  2. GraphService.ready() resolves before the graph is actually ready for the specific node lookup. GraphService.initAsync awaits storage.load() (which only sets lastSyncId) and one getAdjacentNodes('frontier') call. It does NOT pre-load all nodes. Lazy load of @neo-opus-ada happens on first access. If bindAgentIdentity hits it before the SQLite connection is fully warmed or something similar, it could return null.

  3. bindAgentIdentity throws, catch returns null. The try/catch in Server.mjs:bindAgentIdentity catches any thrown error and returns null silently (only logs via the gated logger). If something in await GraphService.ready() or getNode throws in the boot-time context but not the tool-dispatch context, we'd see exactly this behavior.

  4. Async timing within the self-heal block. My #10182 self-heal awaits bindAgentIdentity per dispatch. If the await returns null every time (same underlying issue as #1-#3), the heal never succeeds. Each dispatch re-runs it, each returns null.

  5. Something else entirely — e.g., there are two singleton instances of GraphService due to mixed import paths, or ai/services.mjs vs Server.mjs imports resolve differently under some condition.

The Architectural Reality

Files touched by the fix + diagnostic work:

  • ai/mcp/server/memory-core/config.mjs (line 56, debug: false) — the hardcoded gate
  • ai/mcp/server/memory-core/logger.mjs (lines 8-22) — the level-wrapping logic
  • ai/mcp/server/memory-core/Server.mjs:
    • initAsync (lines 61-125, boot sequence)
    • resolveStdioIdentity (lines 144-159)
    • bindAgentIdentity (lines 206-222)
    • Dispatch block with #10182's self-heal (lines 336-366)
    • logIdentityStatus (lines 228-238)
  • ai/graph/Database.mjsgetAdjacentNodes lazy-load + vicinityLoadedNodes cache
  • ai/graph/storage/SQLite.mjsloadNodeVicinitySync + load

Claude Desktop MCP log path: ~/Library/Logs/Claude/mcp-server-neo-mjs-memory-core.log. Stderr fd2 already redirected here (confirmed via lsof), so enabling logger.* output automatically surfaces traces in this file.

The Fix

Two parts, one ticket, two PRs if that's cleaner:

Part 1 — Enable configurable debug logging (small, ~5 lines).

ai/mcp/server/memory-core/config.mjs line 56:

// Before:
debug: false,

// After:
debug: process.env.NEO_MEM_DEBUG === 'true',

Launch MCP server with NEO_MEM_DEBUG=true in the env block of claude_desktop_config.json (memory-core entry) to enable. Default stays false — no behavior change for normal operation.

Consider applying the same pattern to the other three MCP servers' config.mjs (knowledge-base, github-workflow, neural-link) for symmetry, or leave as a one-off fix here until another server needs it.

Part 2 — Diagnose + fix the binding null (medium, depends on what Part 1 reveals).

With debug enabled, restart Claude Desktop. Tail ~/Library/Logs/Claude/mcp-server-neo-mjs-memory-core.log. Grep for the four binding-trace lines noted above. Expected diagnostic chain:

  • [neo-memory-core MCP] Identity: <userId> via <source> — <bound status> should reveal whether logIdentityStatus was called with a bound or unbound identity at boot
  • If unbound: AgentIdentity graph lookup failed for @<login>: <error.message> surfaces whether bindAgentIdentity's try/catch fired + the error message
  • On first list_messages dispatch: Identity self-healed: <userId> → <nodeId> fires if the heal worked, OR Identity self-heal attempt failed: <error> fires if the rebind threw

Based on what the logs reveal, the fix lands in this same ticket or spawns a narrower follow-up. Candidate fixes (depending on root cause):

  • If hypothesis 1 (stuck vicinity cache): fix Database.mjs:getAdjacentNodes to not mark-loaded when vicinity.nodes.length === 0
  • If hypothesis 2 (ready-but-not-ready): either extend GraphService.ready() semantics to gate on a fuller graph-load signal, or retry in bindAgentIdentity with a small backoff
  • If hypothesis 3 (silent throw): fix whatever bindAgentIdentity throws on at boot; logs will name the offending error
  • If hypothesis 4 (inherited from 1-3): resolves with the upstream fix
  • If hypothesis 5 (duplicate singletons): audit the Neo.setupClass gatekeeper for memory-core-graph or fix the import paths

Acceptance Criteria

  • aiConfig.debug is overridable via NEO_MEM_DEBUG=true environment variable in ai/mcp/server/memory-core/config.mjs
  • When NEO_MEM_DEBUG=true, the startup sequence logs Identity: <x> via <source> — <bound status> to ~/Library/Logs/Claude/mcp-server-neo-mjs-memory-core.log (or equivalent harness stderr sink)
  • Root cause of the bindAgentIdentity null-return identified and documented in the ticket (update this body or post a comment)
  • Fix lands such that, on a fresh Claude Desktop restart with NEO_MEM_DEBUG=false, list_messages returns without the "no agent identity context bound" error
  • add_memory response carries a populated mailbox: {unreadCount, latestPreview} block when messages exist in the caller's inbox
  • Native A2A handshake: @neo-opus-ada sends add_message({to: 'AGENT:*', subject: 'post-fix handshake', ...}), @neo-gemini-pro sees it via list_messages({box: 'inbox'}). Both directions work.
  • The existing #10181 AC item for a dedicated Server.mjs regression test is still tracked via #10183 (MCP test harness); this ticket does not re-duplicate that AC.

Out of Scope

  • Building the MCP Server integration test harness — owned by #10183.
  • Retro-enabling debug on the other MCP servers' config.mjs — optional consistency pass; this ticket focuses on memory-core where the blocking issue lives.
  • Full diagnostic-logging framework upgrades — if the simple env-var flip is enough to diagnose, no need for a larger ergonomics overhaul. File separately if the pattern repeats.

Avoided Traps

  • "Ship another self-heal patch without diagnostic visibility." Rejected. #10182 already demonstrated that without logs, we're guessing at the right fix. Part 1 (enable logs) must land before any further binding-path code change.
  • "Add unconditional console.error calls everywhere in the binding path." Rejected — production log noise. The existing gated logger.* calls are correctly scoped; they just need a runtime toggle.
  • "Treat this as a 'works on my machine' environment issue and move on." Rejected — the failure is reproducible across 4 restarts with identical symptoms. Deterministic bugs deserve deterministic investigations.
  • "Patch bindAgentIdentity to log via console.error directly, bypassing the gated logger." Considered. Works as a fast diagnostic but creates a dedicated debug-only code path that has to be unwound after the fix lands. Cleaner to fix the gate properly — that's Part 1.

Handoff Context

This ticket is intentionally written for fresh-session pickup by another agent (Gemini 3.1 Pro was tentatively identified as a good fit — she has deep recent substrate context from #10177 / #10183 and is in the same mailbox-A2A operational arc). Origin session below preserves the full diagnostic chain from 2026-04-22: four Claude Desktop restarts, SQLite state inspection, MCP process env verification, file-level grep confirmation of the patch landing. Memory Core query by session ID surfaces the complete prior-session decision trail without re-derivation.

If picking this up from Antigravity: the binding failure symptomatically reproduces on that harness too (per earlier empirical handshake attempts in session 7ee23599-...). The fix benefits both harnesses equally.

Related

  • Parent: #10139 (Mailbox A2A primitive epic)
  • Builds on: #10181 (closed by #10182 at the PR level but the underlying bug persists), #10182 (self-heal PR — merged, insufficient alone), #10174 (mailbox runtime fix that originally exercised the binding path)
  • Adjacent: #10183 (MCP test harness — will cover this class of bug end-to-end once built)
  • Not related to: #10179 / #10180 (orthogonal mailbox polish tickets)

Origin Session ID: 7ee23599-3f94-4f75-b54c-97ba92c9aaef

tobiu closed this issue on Apr 22, 2026, 7:05 PM
tobiu referenced in commit 91516a3 - "fix(memory-core): resolve boot-time agent identity binding race (#10184) (#10185) on Apr 22, 2026, 7:05 PM
tobiu referenced in commit 0165814 - "fix(ai): resolve bindAgentIdentity boot-time race condition (#10241) (#10242) on Apr 23, 2026, 6:00 PM