LearnNewsExamplesServices
Frontmatter
id13488
titleSelf-diagnosing MCP identity binding: named fail-closed error
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-opus-ada
createdAtJun 19, 2026, 1:08 AM
updatedAtJun 19, 2026, 6:38 AM
githubUrlhttps://github.com/neomjs/neo/issues/13488
authorneo-opus-ada
commentsCount3
parentIssue13012
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 19, 2026, 6:38 AM

Self-diagnosing MCP identity binding: named fail-closed error

Closed v13.1.0/archive-v13-1-0-chunk-3 enhancementaiarchitecturemodel-experience
neo-opus-ada
neo-opus-ada commented on Jun 19, 2026, 1:08 AM

Context

A maintainer's memory-core MCP identity silently regressed to a stale handle (a prior agent name that no longer maps to a seeded AgentIdentity node). Every identity-bound tool — list_messages, query_recent_turns, add_memory, add_message — failed closed with the bare string "no agent identity context bound", naming neither the offending value nor a remediation. Diagnosing it cost a full working session and two harness restarts.

Squarely in the agent-os stability / self-healing focus (parent #13012).

Release classification: boardless (agent-os stability/self-healing — not v13 release-blocking).

Scope (item a): this ticket is the named fail-closed error (legibility). The proactive boot / healthcheck identity-status surface (item b) is split to #13493.

The Problem

The strict, identity-gated A2A / memory-core services reject calls when no AgentIdentity node is bound, throwing a bare "Cannot <op>: no agent identity context bound." that:

  1. does not name the value it tried,
  2. does not distinguish unset from set-but-unresolvable (the stale/regressed-handle case), and
  3. does not point at a remediation.

Two failure classes converge on the same opaque output — a missing var, and a stale/regressed var (what occurred here).

The Architectural Reality

  • The hard error originates in the strict identity-gated services — ~16 guard sites that throw Cannot <op>: no agent identity context bound: ai/services/memory-core/MailboxService.mjs (8), WakeSubscriptionService.mjs (6), PermissionService.mjs (2). Each does if (!RequestContextService.getAgentIdentityNodeId()) throw ….
  • The attempted userId / source remain on the active RequestContextService even when the graph-node binding is null — so the rich message is constructible at the throw sites.
  • (Correction of the original draft: StdioIdentityResolver returns the handle unvalidated, and AuthMiddleware.mjs:94 is the anti-spoof guard — neither is the unbound-error origin. See the pinned correction comment.)

The Fix

A shared RequestContextService.unboundIdentityError(operation) helper that names the attempted handle + source + remediation, distinguishing present-but-unresolvable (stale/renamed/unseeded) from truly-unresolved (single-tenant). Replace the ~16 generic throws with it. The recognizable "no agent identity context bound" prefix is preserved (existing substring/regex assertions hold); fail-closed posture unchanged (message-only).

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
identity-gated services' unbound-error string RequestContextService.unboundIdentityError Enrich: name value + unset/unresolvable + remediation Current fail-closed behavior preserved (message-only) inline JSDoc this session's opaque-error trace

Decision Record impact

none — improves diagnostics of the existing identity-resolution design; no change to how identity binds. Aligned with #10692 + #11318.

Acceptance Criteria

  • Identity-gated failure emits a structured message naming the attempted handle and distinguishing unset from set-but-unresolvable, with a remediation pointer.
  • Fail-closed posture unchanged; recognizable prefix preserved (existing assertions hold).
  • Unit coverage for: present-but-unresolvable (stale handle), truly-unresolved, and returns-Error-not-throws.
  • No identity value beyond the operator-controlled handle is logged.

Out of Scope

  • Boot / healthcheck identity-status surface (item b) → #13493.
  • The harness config-recovery mechanism that restored a stale snapshot (separate harness/operator note).
  • Auto-repairing a stale identity; changing how identity binds.

Avoided Traps

  • Auto-binding the gh-fallback identity when the env handle is unresolvable — rejected: silently binding the wrong identity is worse than failing closed; the fix is legibility.
  • Relaxing fail-closed — rejected: the security posture is correct; only the diagnostics are deficient.

Related

  • Parent epic: #13012. Item b: #13493. Delivered by PR #13494.
  • Adjacent: #13475, #13289, #13432, #11318.

Handoff Retrieval Hints

  • Retrieval Hint: MailboxService unboundIdentityError no agent identity context bound