diff --git a/src/ClaudeDo.Worker/CLAUDE.md b/src/ClaudeDo.Worker/CLAUDE.md index c1bec3fe..5b5036e2 100644 --- a/src/ClaudeDo.Worker/CLAUDE.md +++ b/src/ClaudeDo.Worker/CLAUDE.md @@ -256,4 +256,5 @@ list-only — there is no task-level override — and is written via `set_list_c - The worker runs standalone — start it separately from the UI. Loopback only (127.0.0.1). - `--permission-mode auto` by default; legacy `bypassPermissions` settings map to `auto` at dispatch time. `acceptEdits`, `plan`, `default` pass through unchanged. - Worktree branches follow `claudedo/{id}`. +- **Both MCP servers run `Stateless = true`** (`Program.cs`). No `Mcp-Session-Id`, so a worker restart can't 404 a session that outlives it, and a 2026-07-28 client isn't refused back to the initialize handshake. The trade: **no** sampling, elicitation, resource subscriptions, unsolicited notifications, or legacy SSE endpoint — don't reach for those. In-tool progress notifications ride the POST's own response stream and work in both modes. All three consumers (`TaskRunner`, `PlanningSessionManager`, the installer's `RegisterMcpStep`) already use `type: "http"`. - **An MCP tool that can run longer than ~5s reports progress.** Staying silent lets the MCP client abort after 300s idle while the worker keeps working — the caller sees an abort even though the operation is still running. `Lifecycle/ProgressReporter` is the one implementation (elapsed-time reporting via `RunAsync`, per-item `i/n` via `ReportItem`) — thread an `IProgress? progress = null` parameter through instead of writing another polling loop. diff --git a/src/ClaudeDo.Worker/Program.cs b/src/ClaudeDo.Worker/Program.cs index db336c68..131778fa 100644 --- a/src/ClaudeDo.Worker/Program.cs +++ b/src/ClaudeDo.Worker/Program.cs @@ -201,7 +201,13 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddMcpServer() - .WithHttpTransport() + // Stateless: no Mcp-Session-Id, so a worker restart doesn't 404 the sessions that + // outlive it (ConPTY tiles in the UI process, externally registered claude sessions), + // and a 2026-07-28 client isn't refused back to the initialize handshake. Nothing here + // needs the stateful-only features (sampling, elicitation, resource subscriptions, + // unsolicited notifications); in-tool progress works in both modes. Implies no legacy + // SSE endpoint — setting EnableLegacySse alongside this throws at startup. + .WithHttpTransport(o => o.Stateless = true) .WithTools() .WithTools() .WithTools(); @@ -342,7 +348,7 @@ if (cfg.ExternalMcpPort > 0) externalBuilder.Services.AddScoped(); externalBuilder.Services.AddScoped(); externalBuilder.Services.AddMcpServer() - .WithHttpTransport() + .WithHttpTransport(o => o.Stateless = true) .WithRequestFilters(f => f.AddCallToolFilter(ExternalMcpExceptionFilter.Wrap)) .WithTools() .WithTools()