You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(planning): reconcile connection-resilience docs and add leftovers inventory
Backend-resilience and pool-invalidation docs still described shipped work
(dcc2977) as pending, and clone-auth had drifted the same way. Update all
five to reflect what actually landed, add a dedicated leftovers doc for
still-parked/open items, and scope web-admin clone parity as required work
(Phase 5) instead of a deferred stub.
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Copy file name to clipboardExpand all lines: docs/planning/backend-connection-resilience-test.md
+12-15Lines changed: 12 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,11 +16,13 @@
16
16
| C HA idle |**SKIPPED**|
17
17
| D unmatched |**SKIPPED** — grant-layer "did you mean", never reached classifier |
18
18
19
-
Confounders: Tauri-watch rebuild wiped inbound sessions (`POST /mcp` → 404 until MCP reload). After reload, 6 unpinned roots required one `mcpmux_set_workspace_root` to *this* repo before invoke was possible. That pin was session disambiguation, not the reconnect path.
19
+
Confounders (A/B run, before `dcc2977`): Tauri-watch rebuild wiped inbound sessions (`POST /mcp` → 404 until MCP reload). After reload, 6 unpinned roots required one `mcpmux_set_workspace_root` to *this* repo. That pin was session disambiguation, not the reconnect path.
20
20
21
-
**Inbound 404 after gateway rebuild (Decision 3, follow-on):** process death drops `LocalSessionManager`. `POST /mcp` with a stale `Mcp-Session-Id` is a spec-correct 404. [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) and the [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/issues/1708) do **not** re-`initialize` on that 404 (they stay stuck on the dead session id). Do not persist sessions. Recovery is Reload MCP once. `/health` staying 200 means the gateway is up; 404 is not "gateway is down."
21
+
Follow-on (`dcc2977`) playbook: [`pool-invalidation-and-session-survival-test.md`](./pool-invalidation-and-session-survival-test.md) — E/F/H PASS (hold-then-pin, no `set_workspace_root`), G BLOCKED.
22
22
23
-
**Header pin after reload (Decision 4):** a non-empty `X-Mcpmux-Workspace` is held across initialize (no session id yet) and applied when `mcp-session-id` appears. After Reload MCP, `mcpmux_list_servers` should be bound for this repo without `set_workspace_root`. Empty `${workspaceFolder}` is still the Agents-window hole.
23
+
**Inbound 404 after gateway rebuild:** process death drops `LocalSessionManager`. `POST /mcp` with a stale `Mcp-Session-Id` is a spec-correct 404. [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) and the [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/issues/1708) do **not** re-`initialize` on that 404. Do not persist sessions. Recovery is Reload MCP once. `/health` staying 200 means the gateway is up.
24
+
25
+
**Header pin after reload:** a non-empty `X-Mcpmux-Workspace` is held across initialize and applied when `mcp-session-id` appears. Empty `${workspaceFolder}` is still the Agents-window hole.
24
26
25
27
---
26
28
@@ -168,19 +170,14 @@ A permission / not-found error that never hits the backend is also acceptable
Playbook for [`pool-invalidation-and-session-survival.md`](./pool-invalidation-and-session-survival.md). Same mux-only / no-`pkill` rules.
172
-
173
-
### Config save reconnect
174
-
175
-
Change an enabled server's header or stdio arg in Configure, **do not** click Retry Connection, invoke a tool on that server. Expect `reconnect_fresh completed ok=true` from `[ServerConfigHandler]` and the new value in use.
176
-
177
-
### Rebuild 404
173
+
Dedicated playbook + Aug 20 results: [`pool-invalidation-and-session-survival-test.md`](./pool-invalidation-and-session-survival-test.md). Same mux-only / no-`pkill` rules.
178
174
179
-
Trigger a Rust rebuild. `curl -sf http://127.0.0.1:45818/health` stays 200. Existing Cursor chats 404 on `/mcp`. Reload MCP once. Do not treat 404 as a dead gateway.
180
-
181
-
### Header pin
182
-
183
-
Multi-folder Cursor window with a real `X-Mcpmux-Workspace` pointing at this repo. Reload MCP. `mcpmux_list_servers` is bound, not 6-way `bindable`. `set_workspace_root` is not the success path.
175
+
| Case | Result |
176
+
| ---- | ------ |
177
+
| E header pin |**PASS** — hold then pin; this repo `ready`|
178
+
| F stdio refuse / Case B regression |**PASS** — `reconnect_fresh`, no OAuth / no HTTP fallback |
179
+
| G config-save |**BLOCKED** — admin `:45819` 401 (no CF probe headers). Do not sqlite-edit |
180
+
| H rebuild 404 |**PASS** off the 11:36 recycle — `/health` 200, 404 is expected noise, Case E pin ~3s later |
Copy file name to clipboardExpand all lines: docs/planning/backend-connection-resilience.md
+10-7Lines changed: 10 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,9 +16,9 @@ Traced live (code reading, not guessed):
16
16
17
17
-`RoutingService::call_tool`'s error branch only retries when `is_auth_error(&err_str)` matches (`crates/mcpmux-gateway/src/pool/routing.rs:666,816`) — indicators are `"401"`, `"unauthorized"`, etc. A literal `"connection closed"` never matches, so the `else` branch (`routing.rs:776-787`) just returns the raw error with zero retry.
18
18
-`ServerInstance::is_healthy()` (`crates/mcpmux-gateway/src/pool/instance.rs:397-399`) is `state == Connected && client.is_some()` — a state flag, not a liveness check. `PoolService::connect_server()` (`crates/mcpmux-gateway/src/pool/service.rs:284-294`) trusts it and reuses the instance without ever touching the transport again. A backend that silently died still reads "healthy" until a real call fails against it.
19
-
-`PoolService::reconnect_instance()` (`service.rs:434-459`) unconditionally calls`ConnectionService::reconnect_after_oauth()` (`crates/mcpmux-gateway/src/pool/connection.rs:459`). For a `TransportType::Stdio` instance, that function hits a fallback branch that **warns and builds an `Http` transport anyway** (`connection.rs:499-502`, `"Unexpected STDIO transport for OAuth reconnection, defaulting to HTTP"`) — i.e. today's only reconnect path is not just auth-only, it actively mis-reconnects stdio backends if it's ever invoked for one.
19
+
-`PoolService::reconnect_instance()` (`service.rs:434-459`) unconditionally called`ConnectionService::reconnect_after_oauth()` (`crates/mcpmux-gateway/src/pool/connection.rs:459`). For a `TransportType::Stdio` instance, that function hit a fallback that **warned and built an `Http` transport anyway** (`"Unexpected STDIO transport for OAuth reconnection, defaulting to HTTP"`). At investigation that was the only reconnect path and it mis-reconnected stdio. **Shipped since (`dcc2977`):**stdio → `Failed` (`stdio cannot reconnect via OAuth; use reconnect_fresh`). HTTP fallback deleted.
20
20
-`InstanceKey::stdio()`/`::http()` (`crates/mcpmux-gateway/src/pool/instance.rs:266,279`) take `command`/`args`/`env`/`headers` as **unused, underscore-prefixed params** — only `space_id` and a debug `description` string survive on the stored key. The original transport config is *not* retained on `ServerInstance` after `connect_server()` returns, so any new reconnect path can't rebuild it from the instance alone — it has to re-resolve from DB, the same way `connect_server()`'s caller does today.
21
-
- The one place that already does this correctly is `retry_connection` (`apps/desktop/src-tauri/src/commands/server_manager.rs:516-547`): `pool_service.remove_instance()` then `connect_enabled_server()`, which re-resolves transport config fresh from DB and calls `connect_server()` again — transport-agnostic, unlike `reconnect_after_oauth()`. That path is Tauri-desktop-only today; `RoutingService`(gateway-internal, used by both desktop and headless/web-admin) has no equivalent.
21
+
- The one place that already did this correctly at investigation was `retry_connection` (`apps/desktop/src-tauri/src/commands/server_manager.rs:516-547`): `remove_instance()` then `connect_enabled_server()`. **Shipped since:**`PoolService::reconnect_fresh` / `reconnect_fresh_from_db`; `RoutingService`uses it for transport-closed; admin `retry_connection` uses the same helper.
22
22
-`mcpmux_set_workspace_root` (`crates/mcpmux-gateway/src/services/meta_tools/set_workspace_root.rs:53-114`) only re-seeds the **inbound**`SessionRootsRegistry` and fires `tools/list_changed` — it never touches `PoolService` or any backend instance. The "fix" working is a side effect of Cursor opening a fresh inbound rmcp session (hence the new `session_id`) — nothing in the gateway explicitly reconnects the dead backend.
23
23
- Separately: `mcpmux_bind_current_workspace` (`crates/mcpmux-gateway/src/services/meta_tools/bind_workspace.rs:175-181`) loads the FeatureSet name via `feature_set_repo.get(&fs_id.to_string())`, and on `Ok(None)` (nonexistent id) falls back to `fs_id.to_string()` as the display name **instead of rejecting** — then proceeds to `binding_repo.create()`. `WorkspaceBindingRepository::create()` (`crates/mcpmux-storage/src/repositories/workspace_binding_repository.rs:236-268`) wraps the parent `INSERT` and `rewrite_fs_for_binding()`'s junction `INSERT`s (`workspace_binding_repository.rs:142-160`) in one transaction against `workspace_binding_feature_sets.feature_set_id → feature_sets(id)` — a nonexistent `feature_set_id` surfaces as a raw SQLite `FOREIGN KEY constraint failed`, not a clean domain error. The transaction wrapping means it fails atomically (no partial binding), but the error is the wrong shape for an agent to act on.
24
24
-**At investigation time the pool already had per-instance telemetry and nothing populated it at call time.**`InstanceStats` carries `connected_at`, `consecutive_failures`, `requests_served`, and `last_error`, and `record_success()` / `record_failure()` existed with **zero call sites**. Phase 1 wired them from `call_tool`. `mark_failed()` (flips `state` to `Failed`) stays connect/reconnect-only — a call-time `-32000` on a `Connected` instance used to leave `is_healthy()` true and `consecutive_failures` at 0. That is no longer true for call stats; the state-flag lie (Decision 6) is still deferred.
| D unmatched |**SKIPPED** — grant-layer "did you mean", never reached the classifier |
229
229
230
-
Tauri-watch rebuild mid-test wiped Streamable HTTP sessions (`POST /mcp` → 404). Reload MCP once, then pin this repo if `list_servers` reports unpinned roots. Do not use `set_workspace_root` as the reconnect itself.
230
+
Tauri-watch rebuild mid-test wiped Streamable HTTP sessions (`POST /mcp` → 404). Reload MCP once. A non-empty `X-Mcpmux-Workspace` is held until `mcp-session-id` exists, then pinned (`dcc2977`). Empty header still skips. Do not use `set_workspace_root` as the reconnect itself.
231
+
232
+
Follow-on playbook: [`pool-invalidation-and-session-survival-test.md`](./pool-invalidation-and-session-survival-test.md) — E/F/H PASS, G BLOCKED (admin 401).
|[`crates/mcpmux-gateway/src/pool/instance.rs`](../../crates/mcpmux-gateway/src/pool/instance.rs)|`is_healthy()` still state-only (Decision 6, deferred); `record_success()`/`record_failure()` wired from `routing.rs` (Decision 9); `mark_failed()` stays connect/reconnect-only (Decision 10) |
241
-
|[`crates/mcpmux-gateway/src/pool/connection.rs`](../../crates/mcpmux-gateway/src/pool/connection.rs)|`reconnect_after_oauth()` L459; stdio-to-HTTP fallback warn L499-502 — root of why `reconnect_instance()` is unsafe for the widened trigger|
-[`docs/planning/backend-connection-resilience-test.md`](./backend-connection-resilience-test.md) — manual playbook + Aug 20 results (A/B pass; C skipped; D never reached classifier)
259
261
-[`docs/planning/aug14-gateway-ops-bugs.md`](./aug14-gateway-ops-bugs.md) — this branch's parent investigation (inbound session keep-alive, log noise); marked stale by this doc (Decision 5)
260
-
-[`docs/planning/pool-invalidation-and-session-survival.md`](./pool-invalidation-and-session-survival.md) — follow-on after Aug 20 verification: config-save `reconnect_fresh`, stdio OAuth refuse, inbound 404 recovery, trust `X-Mcpmux-Workspace`
261
-
-[`docs/planning/clone-auth-header-config-editing.md`](./clone-auth-header-config-editing.md) — separate "Connection closed" bug (stale pool reuse after a config edit), same `PoolService::connect_server()` healthy-reuse code path this doc's Decision 1/2 also touches
-[`docs/planning/resilience-routing-leftovers.md`](./resilience-routing-leftovers.md) — collects the Decision 5–7 deferrals (matcher freeze, `is_healthy()` heartbeat, unused `ClientPool`) plus other parked/open items in one inventory
264
+
-[`docs/planning/clone-auth-header-config-editing.md`](./clone-auth-header-config-editing.md) — clone UI still planning; Decision 4 pool half shipped in the follow-on
262
265
-[`docs/planning/deny-by-default-bindable-callers.md`](./deny-by-default-bindable-callers.md) — established rmcp's inbound keepalive as expected client-hang behavior (Jun 29), informed PR #221's Decision 3
263
266
-[`docs/planning/cursor-workspace-routing-bridge.md`](./cursor-workspace-routing-bridge.md) — `set_workspace_root`/`SessionRootsRegistry` design this doc confirms is inbound-only, not a backend reconnect mechanism
0 commit comments