Deterministic MCP namespace attachment (from incident: client transport-pinning to obsolete cohort head 22698c1) #689

Open
opened 2026-07-12 21:22:33 -05:00 by jcwalker3 · 0 comments
Owner

Promoted from a bare incident record to the canonical tracker for deterministic namespace attachment. The original incident text is preserved verbatim below; scope and acceptance criteria are added so the defect has a durable, testable home.

Related: epic #887, #708, #686, #678, #900.

Original incident record (unchanged)

Description

A client-transport-pinning defect was detected where the MCP cohort remained pinned to an obsolete head 22698c1 despite client reconnects spawning newer processes.

Remediation

Ensure all client/server components correctly disconnect, clear and re-initialize their runtime cohort hashes, and do not preserve stale git heads or leases across sessions.

Scope

Which cohort a client session binds to, and whether that choice is verifiable. This is distinct from whether any namespace attaches at all, which is #708, and from how many cohorts exist, which is #900. The incident above is the reference failure: reconnects spawned newer processes and the session stayed pinned to an obsolete one, with nothing in the handshake able to reveal it.

This defect has since recurred in the same shape. Author cycles were blocked across eight consecutive runs while sessions remained bound to a cohort whose startup_head trailed live master, and the binding could not be inspected or re-pointed from inside a session. The reconcile_id reported by gitea_assess_master_parity.post_restart_reconcile was the only signal that distinguished a genuinely new attachment from a stale one, and it is incidental telemetry rather than an enforced attachment identity.

Coverage gap this tracker closes

Assessed against the deterministic-attachment requirement set at master a4c73766f4b0cc32f7c3808688eceeb6fee74335:

Requirement Prior state
Handshake exposes cohort ID, PID, startup SHA, endpoint, config fingerprint PARTIAL — #678 AC2 specifies PID/session, startup SHA, current master SHA, boundary status on the reconnect result, not the handshake; #708 AC5 adds Connected status, attached namespaces, discovery cache age. Cohort ID, endpoint, and configuration fingerprint are absent
Clients reject stale or ineligible cohorts PARTIAL — #708 AC1 and AC2 detect Connected-with-no-namespaces and either auto-attach or return a typed blocker; that is absence of attachment, not staleness of an attachment that succeeded. This issue's remediation line named the requirement but carried no acceptance criterion
Reconnection binds to the cohort matching authoritative parity UNCOVERED
Stale attachment cannot silently retain its old reconcile_id UNCOVERED
Regression proves repeated reconnects never rebind to an obsolete daemon UNCOVERED

Acceptance criteria

  • The client/server handshake exposes cohort ID, PID, startup SHA, endpoint, and configuration fingerprint, and a session can read the identity of the cohort it is actually bound to.
  • A client refuses to bind to a cohort that is stale or ineligible, and reports the refusal as a typed condition distinct from the Connected-but-unattached condition owned by #708.
  • Reconnection binds to the cohort whose startup SHA matches authoritative runtime parity as reported by gitea_assess_master_parity; if no such cohort exists, the client fails closed rather than binding to the newest available.
  • A stale attachment cannot silently retain its previous reconcile_id: re-attachment either issues a new one or reports that the binding did not change, so a caller can always distinguish a genuinely new attachment from a preserved stale one.
  • Runtime cohort hashes, cached tool lists, and session-scoped bindings are cleared on disconnect, so no stale head or lease survives into a new session.
  • The bound cohort's identity appears in diagnostics alongside parity, so agreement between the two can be checked rather than assumed.
  • Regression: after N reconnects against a cohort set containing obsolete daemons, the session is never bound to an obsolete one.
  • Regression: reproduce the incident shape — reconnects spawn newer processes while an obsolete cohort remains resident — and assert the session binds to the parity-matching cohort.
  • Regression: a session bound to a cohort that later goes stale detects the change on its next attachment check rather than continuing to serve.

Non-goals

  • Connected-but-namespaces-empty detection and recovery, owned by #708.
  • Bounding how many cohorts exist, and draining or reaping superseded ones, owned by #900.
  • Provenance of manually launched servers, owned by #686.
  • Exposing a callable reconnect action to clients that lack one, owned by #678.
  • Restart authorization and blast radius, owned by #655.

Duplicate verdict

NOT a duplicate. Verified by reading the bodies, comments, and acceptance criteria of #708, #686, and #678. #708 explicitly asks not to be collapsed with transport-pinning trackers and scopes itself to Connected-versus-attached; comment 11469 on #708 states that server runtime parity and client tool attachment are independent health axes, which is precisely the axis this issue owns.

Promoted from a bare incident record to the canonical tracker for deterministic namespace attachment. The original incident text is preserved verbatim below; scope and acceptance criteria are added so the defect has a durable, testable home. Related: epic #887, #708, #686, #678, #900. ## Original incident record (unchanged) ### Description A client-transport-pinning defect was detected where the MCP cohort remained pinned to an obsolete head 22698c1 despite client reconnects spawning newer processes. ### Remediation Ensure all client/server components correctly disconnect, clear and re-initialize their runtime cohort hashes, and do not preserve stale git heads or leases across sessions. ## Scope Which cohort a client session binds to, and whether that choice is verifiable. This is distinct from whether any namespace attaches at all, which is #708, and from how many cohorts exist, which is #900. The incident above is the reference failure: reconnects spawned newer processes and the session stayed pinned to an obsolete one, with nothing in the handshake able to reveal it. This defect has since recurred in the same shape. Author cycles were blocked across eight consecutive runs while sessions remained bound to a cohort whose `startup_head` trailed live master, and the binding could not be inspected or re-pointed from inside a session. The `reconcile_id` reported by `gitea_assess_master_parity.post_restart_reconcile` was the only signal that distinguished a genuinely new attachment from a stale one, and it is incidental telemetry rather than an enforced attachment identity. ## Coverage gap this tracker closes Assessed against the deterministic-attachment requirement set at master `a4c73766f4b0cc32f7c3808688eceeb6fee74335`: | Requirement | Prior state | | --- | --- | | Handshake exposes cohort ID, PID, startup SHA, endpoint, config fingerprint | PARTIAL — #678 AC2 specifies PID/session, startup SHA, current master SHA, boundary status on the reconnect result, not the handshake; #708 AC5 adds Connected status, attached namespaces, discovery cache age. Cohort ID, endpoint, and configuration fingerprint are absent | | Clients reject stale or ineligible cohorts | PARTIAL — #708 AC1 and AC2 detect Connected-with-no-namespaces and either auto-attach or return a typed blocker; that is absence of attachment, not staleness of an attachment that succeeded. This issue's remediation line named the requirement but carried no acceptance criterion | | Reconnection binds to the cohort matching authoritative parity | UNCOVERED | | Stale attachment cannot silently retain its old reconcile_id | UNCOVERED | | Regression proves repeated reconnects never rebind to an obsolete daemon | UNCOVERED | ## Acceptance criteria - The client/server handshake exposes cohort ID, PID, startup SHA, endpoint, and configuration fingerprint, and a session can read the identity of the cohort it is actually bound to. - A client refuses to bind to a cohort that is stale or ineligible, and reports the refusal as a typed condition distinct from the Connected-but-unattached condition owned by #708. - Reconnection binds to the cohort whose startup SHA matches authoritative runtime parity as reported by `gitea_assess_master_parity`; if no such cohort exists, the client fails closed rather than binding to the newest available. - A stale attachment cannot silently retain its previous `reconcile_id`: re-attachment either issues a new one or reports that the binding did not change, so a caller can always distinguish a genuinely new attachment from a preserved stale one. - Runtime cohort hashes, cached tool lists, and session-scoped bindings are cleared on disconnect, so no stale head or lease survives into a new session. - The bound cohort's identity appears in diagnostics alongside parity, so agreement between the two can be checked rather than assumed. - Regression: after N reconnects against a cohort set containing obsolete daemons, the session is never bound to an obsolete one. - Regression: reproduce the incident shape — reconnects spawn newer processes while an obsolete cohort remains resident — and assert the session binds to the parity-matching cohort. - Regression: a session bound to a cohort that later goes stale detects the change on its next attachment check rather than continuing to serve. ## Non-goals - Connected-but-namespaces-empty detection and recovery, owned by #708. - Bounding how many cohorts exist, and draining or reaping superseded ones, owned by #900. - Provenance of manually launched servers, owned by #686. - Exposing a callable reconnect action to clients that lack one, owned by #678. - Restart authorization and blast radius, owned by #655. ## Duplicate verdict NOT a duplicate. Verified by reading the bodies, comments, and acceptance criteria of #708, #686, and #678. #708 explicitly asks not to be collapsed with transport-pinning trackers and scopes itself to Connected-versus-attached; comment 11469 on #708 states that server runtime parity and client tool attachment are independent health axes, which is precisely the axis this issue owns.
jcwalker3 added the status:readymcp-healthstale-runtimetype:bug labels 2026-07-12 21:22:33 -05:00
jcwalker3 changed title from incident: MCP client transport-pinning to obsolete cohort head 22698c1 to Deterministic MCP namespace attachment (from incident: client transport-pinning to obsolete cohort head 22698c1) 2026-07-24 22:00:06 -05:00
jcwalker3 added status:pr-open and removed status:ready labels 2026-07-25 17:53:16 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#689