Code: #handshake and #race in lib/src/remote/client/one-time-client.ts.
Failure path: connectOnce computes the room's hard deadline (expiry * 1000 + ONE_TIME_EXPIRY_GRACE_MS), but only #core.exchange and #core.exchangeControl enforce it. The steps before them, generateNoiseKeyPair, createNoiseInitiator, writeMessage and #openRendezvous, go through #race, which a failure or close() can interrupt but no timer can. A WebCrypto call that never settles, or a WebSocket that stays CONNECTING, leaves the page on its code screen with no ending. It ends only when the browser gives up on the socket or the person taps Cancel. one-time.md's failure table promises ONE_TIME_LINK_EXPIRED_MESSAGE for "no answer by the room's deadline".
Suggested fix: in connectOnce, arm one timer at the room's hard deadline that calls #fail(ONE_TIME_LINK_EXPIRED_MESSAGE), and clear it at the switch or in #teardown. #race then covers the WebCrypto and socket-open waits as well. To test it, stub createWebSocket with a socket that never opens, advance the clock past the deadline, and assert the expired copy.
Found while trimming #904, which had parked this as a spec "Known gap".
🤖 Generated with Claude Code
Code:
#handshakeand#raceinlib/src/remote/client/one-time-client.ts.Failure path:
connectOncecomputes the room's hard deadline (expiry * 1000 + ONE_TIME_EXPIRY_GRACE_MS), but only#core.exchangeand#core.exchangeControlenforce it. The steps before them,generateNoiseKeyPair,createNoiseInitiator,writeMessageand#openRendezvous, go through#race, which a failure orclose()can interrupt but no timer can. A WebCrypto call that never settles, or a WebSocket that staysCONNECTING, leaves the page on its code screen with no ending. It ends only when the browser gives up on the socket or the person taps Cancel. one-time.md's failure table promisesONE_TIME_LINK_EXPIRED_MESSAGEfor "no answer by the room's deadline".Suggested fix: in
connectOnce, arm one timer at the room's hard deadline that calls#fail(ONE_TIME_LINK_EXPIRED_MESSAGE), and clear it at the switch or in#teardown.#racethen covers the WebCrypto and socket-open waits as well. To test it, stubcreateWebSocketwith a socket that never opens, advance the clock past the deadline, and assert the expired copy.Found while trimming #904, which had parked this as a spec "Known gap".
🤖 Generated with Claude Code