Repository navigation
Description
Decision for Drew (one line): Approve persisting mcpx upstream HTTP session state (session IDs + protocol state, never resolved header secrets) to the owner-only state dir so a cold restart resumes sessions instead of re-initialising every HTTP upstream — yes/no; the graceful-shutdown DELETE question in Phase 3 rides on the same decision.
Design doc: files/designs/code-mcpx-cold-restart-session-resume.md in the keep-the-four-repos-healthy goal workspace. Register: central potential-projects list, same workspace (files/potential-projects.md).
Summary
A takeover preserves upstream HTTP sessions; a cold restart does not. pkgs/mcpx/TODO.md item 3: "The lease table is restored from sessions.json, but the upstream Mcp-Session-Id is kept only in memory and in the takeover handoff, so after a restart each HTTP upstream is initialised again." Roadmap: planning/roadmaps/mcpx.md → Next → "Resume upstream sessions after a cold restart" (status idea).
Costs per cold restart, per HTTP upstream: a fresh initialize handshake, silent loss of upstream per-session state, and one leaked upstream session (the dead daemon never sent DELETE; the new daemon doesn't know the ID — #260 fixed this leak for takeover only).
Proposal: persist the resumable half of each legacy streamable-HTTP upstream session in a new versioned state-dir file (same discipline as internal/daemon/sessions.go: atomic tmp+rename, defaults.PrivateMode, version envelope, ignore-on-mismatch), and let a cold-started daemon take the existing takeover restore path with the record standing in for the socket:
- Record per pool instance: instance key, URL, config fingerprint (hash over session-defining config fields + header/auth names, never values),
HTTPHandoffminus Headers (SessionID,Negotiated), the fullmcpclient.Sessionprotocol state,savedAt. - No resolved header values on disk — the codebase's rule is that handoff headers travel "over the takeover's unix socket only" (
internal/mcpclient/http.go,HTTPHandoffcomment). On restore, headers are re-derived from live config (httpOptions()+resolveAuth()), then the transport/client are seeded with the exact calls takeover makes:ht.Resume(...)andmcpclient.Resume(...)(internal/pool/handoff.go:238,326). No parallel resume implementation. - Save on legacy session establishment and on session-ID change (
internal/mcpclient/http.go:156is where IDs are adopted); save failure is logged, non-fatal — resume is an optimisation. - Scope: legacy-era streamable HTTP only. Modern era (2026-07-28) has no sessions; legacy SSE holds its session in the GET stream (TODO item 2 already accepts re-init there).
Grounding: internal/mcpclient/http.go (sessionID/negotiated in memory only), internal/mcpclient/handoff.go (SessionState(), Resume — the no-handshake client already exists), internal/pool/pool.go:704 (dial() seeds nothing today), internal/daemon/sessions.go (persistence precedent). Sibling of #1384 (stdio supervisor), which names cold-restart HTTP resume as an explicit non-goal — this design is that non-goal, designed separately. Alternatives (do nothing; fold into sessions.json; persist full handoff incl. headers; era-cache-only) and risks (stale-session resume, session IDs as bearer tokens on disk, write amplification, restore-path drift) are argued in the design doc.
Phased breakdown
- Phase 1 — persist and resume. New state file + record type; fingerprint gate; restore via the takeover path. Defining test: session established → daemon SIGKILLed → restarted → first call succeeds, upstream saw no second
initialize, request carried the originalMcp-Session-Id. Filed as a sub-issue of this issue. - Phase 2 — expiry fallback and record hygiene. First request on a resumed session answered 404 (or 401/403 after credential rotation) → drop record, fresh
initialize, retry once, transparently. Server removed from config → record deleted + best-effort upstream DELETE, or logged left-to-expire (the mcpx: takeover orphans a removed server's upstream HTTP session — never DELETEd, left to expire upstream #260 degradation, made explicit). Coordinate with the roadmap's "Legacy client robustness" row (nix#200–Update module golang.org/x/net to v0.55.0 [SECURITY] #203) — this is only the resumed-session slice. Filed as a sub-issue of this issue. - Phase 3 — open questions, NOT filed. Graceful-shutdown semantics (should a restart-intent stop skip the DELETE that
Close→closeSessionsends today?); record TTL; dashboard resumed-vs-fresh visibility; whether takeover should also write the record. The shutdown question is part of Drew's decision above.
agent-note:v1 — filed by the repo design architect (dedicated code job, pass 2, 2026-10-27). Design doc + register entry as linked above. user/review carries the one-line direction decision at the top; Phases 1–2 are filed as sub-issues with queue/queued + task/triage packets enqueued to repo-health-code-pilot; Phase 3 stays as questions in the design doc.
Acceptance criteria
- Drew's decision on persisting upstream HTTP session state (IDs + protocol state only, never resolved header secrets) is recorded on this issue.
- If approved: Phase 1 (mcpx: Phase 1 of #1414 — persist legacy HTTP upstream sessions and resume them after a cold restart #1415) and Phase 2 (mcpx: Phase 2 of #1414 — resumed-session expiry fallback (404/401) and record hygiene #1416) land with their own acceptance criteria met, and the Phase 3 graceful-shutdown DELETE question is answered in the same decision thread.
- If declined: this issue and its phases are closed with the rationale recorded, and
pkgs/mcpx/TODO.mditem 3 is updated to match.
Activity
developing-today commentedon Oct 27, 2026
CODE-504
dezren39 commentedon Oct 27, 2026
Intake review
Fleet monitor intake pass, 2026-10-27 (repo-fleet-monitor).
Related: implementation phases already filed — #1415 (Phase 1: persist + resume legacy HTTP sessions) and #1416 (Phase 2: expiry fallback + record hygiene), both enriched earlier today and queued. Design doc:
files/designs/code-mcpx-cold-restart-session-resume.mdin the goal workspace.Notes: This is the decision parent, not a work item: it already carries priority/normal, effort/l, kind/design and
user/review. The one-line decision in the body (persist session state — yes/no) is genuinely Drew's, so the phases stay queued behind it.Risks: Persisting anything beyond session IDs + protocol state would be a secrets bug — the acceptance criteria restate the never-secrets boundary.
Suggested approach: Drew decides on this issue; on a yes, #1415 is the first claimable step.
dezren39 commentedon Oct 28, 2026
agent-note:v1 — escalated to user (worker-first review attached)
23 remaining items
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass; current-main revalidation)
Verdict: still live; the cold-restart gap is confirmed at
main52fd50d17e750ae84385b5b41414e886eccee95e.pkgs/mcpx/TODO.mditem 2 still says the upstreamMcp-Session-Idis only in memory/takeover handoff, so after cold restart each HTTP upstream is initialized again. No source changes or tests were needed for this triage.Decision and route: Drew’s YES to persist session IDs + protocol state (never resolved headers) is already recorded here (2026-10-28). The existing phases remain the correct route: #1415 (Phase 1) and #1416 (Phase 2); no duplicate tasks filed. Fresh checks: both issues remain OPEN; #1415 is
status/blocked+queue/blocked, task 1483 is blocked, and PR search found no matching implementation PR. #1416 remains parked as task 1484 behind Phase 1.Blocker / ordering: #1415’s current blocker is stale: it still cites the #1414 decision as undecided although the YES is recorded; the 2026-10-31 reconciliation comment on #1415 already names this contradiction. The owner of task 1483 / fleet reconciliation should clear or replace that stale blocker; Phase 2 remains gated until Phase 1 lands. This triage does not take ownership of either implementation task.
Found via: repo-health-code-pilot task 7711 (OpenChamber scheduled lane)