fix: WebSocket RPC hang — nested Effect.gen deadlock in request fiber#109
Conversation
Root cause: yield*-ing an Effect.gen from inside another Effect.gen within the HTTP request fiber caused the inner generator to never start executing. The first yield* statement inside makeWsRpcContext never ran, silently hanging every /ws connection after auth. Fix: - Replaced Effect.gen in makeWsRpcContext with flat pipeline composition (Effect.flatMap/Effect.andThen chaining) - Moved Effect.context<never>() capture to startup via a new WsAppServices service that pre-builds the full app-level context once at boot - Fixed vendored Effect API mismatches: Context.Tag -> Context.Service, removed nonexistent Effect.tapErrorCause calls Also includes layer-wiring fixes for startup services and auth session infrastructure.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8fc126cf85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| yield* Effect.logDebug("auth.websocket upgrade using ticket", { | ||
| path: request.originalUrl, |
There was a problem hiding this comment.
Avoid logging WebSocket tickets in request paths
When clients connect with the new ?wsTicket=... flow, request.originalUrl contains the bearer ticket, so these debug entries persist a live credential in server logs before/after verification. This affects every ticket-authenticated WebSocket upgrade; log a sanitized path or only the boolean hasWebSocketTicket instead of the raw URL.
Useful? React with 👍 / 👎.
| } catch { | ||
| existing = []; |
There was a problem hiding this comment.
Preserve archive data on JSON parse failures
If the todo archive file is malformed or partially written, this catch makes appendToArchive treat it as empty and then writeFileStringAtomically overwrites the archive with only the newly archived items. In that scenario, a normal todo completion permanently discards all previously archived entries; it should fail the mutation or write to a separate recovery path instead of replacing unreadable data.
Useful? React with 👍 / 👎.
Root Cause
A nested
Effect.gen(generator) inside anotherEffect.genwithin the HTTP request fiber never starts executing. Whenyield* makeWsRpcContext(session)was called from the/wsroute handler's generator, the inner generator body was never entered — its firstyield*statement never ran. The same code worked perfectly in the startup fiber (self-test).Fix
Effect.genwith flat pipeline composition inmakeWsRpcContext— usesEffect.flatMap/Effect.andThenchains instead of generatoryield*.Effect.context<never>()to startup — a newWsAppServicestag captures all app-level services once at boot viawsAppServicesLayer, instead of per-request.Context.Tag→Context.Service, removed nonexistentEffect.tapErrorCause.Verification
T3CODE_DEBUG_WS_CONTEXT_SELFTEST=1) passes: all 10 services load, handlers build successfully/wsconnection (authenticated session) now reachesws.rpc handlers builtandws.rpc effect prepared— confirmed in live logs