Fix lost flags after hibernation wake#893
Closed
ask-bonk[bot] wants to merge 2 commits into
Closed
Conversation
…bled predicate Add per-connection control over protocol text frames (CF_AGENT_IDENTITY, CF_AGENT_STATE, CF_AGENT_MCP_SERVERS). Override shouldSendProtocolMessages() to return false for connections that should not receive these frames. This enables binary-only clients (e.g. MQTT devices) to connect without receiving JSON text frames, while still participating in RPC and regular messaging. Changes: - Add shouldSendProtocolMessages(connection, ctx) hook (default: true) - Add isConnectionProtocolEnabled(connection) predicate helper - Update onConnect to skip protocol messages when hook returns false - Update state and MCP broadcasts to exclude no-protocol connections - Generalize connection state wrapping to handle multiple internal flags - Add comprehensive test suite (12 tests) Closes #881
…bernation wake When a DO wakes from hibernation, _rawStateAccessors (an in-memory WeakMap) is empty because the DO was re-instantiated. The previous fallback in isConnectionProtocolEnabled returned true (protocol enabled) even when the _cf_no_protocol flag was persisted in the WebSocket's serialized attachment. This caused broadcastMcpServers() in onStart to send cf_agent_mcp_servers frames to connections that had opted out. Fix both isConnectionProtocolEnabled and isConnectionReadonly to fall back to reading connection.state directly when _rawStateAccessors doesn't have the connection. Before _ensureConnectionWrapped runs, connection.state returns the raw state including internal keys. Fixes the rogue cf_agent_mcp_servers frame reported in #881.
🦋 Changeset detectedLatest commit: c70c7cf The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Here is my analysis and the fix:
Root Cause
When a Durable Object wakes from hibernation, it is re-instantiated — all in-memory state is lost. The
_rawStateAccessorsWeakMap that maps Connection objects to their raw state accessors is empty. However, the internal flags (_cf_no_protocol,_cf_readonly) are still persisted in each WebSocket's serialized attachment and survive hibernation.The problem was in the hibernation wake sequence:
webSocketMessage(ws, msg)on the hibernated DO_rawStateAccessors = new WeakMap())onStart()fires insideblockConcurrencyWhile():mcp.restoreConnectionsFromStorage()runsbroadcastMcpServers()calls_broadcastProtocol()_broadcastProtocol()iterates all connections, callingisConnectionProtocolEnabled(conn)for eachisConnectionProtocolEnabledchecks_rawStateAccessors.get(connection)— returnsundefinedbecause the WeakMap is emptytrue(protocol enabled), ignoring the_cf_no_protocolflag in the serialized attachmentcf_agent_mcp_serversis sent to all connections, including ones that opted outThis is why @genmon saw the rogue frames only when the DO woke after hibernation — the flag was correctly checked during normal operation (when
_rawStateAccessorswas populated), but the fallback path after hibernation wake was broken.Fix
In both
isConnectionProtocolEnabledandisConnectionReadonly, when_rawStateAccessorsdoesn't have the connection, we now fall back to readingconnection.statedirectly. Before_ensureConnectionWrappedruns (which overrides thestategetter to strip internal keys), partyserver'screateLazyConnectiondefinesstateas a getter that returns the raw attachment — including internal flags like_cf_no_protocol.Changes:
packages/agents/src/index.ts:1326-1332—isConnectionReadonlynow reads fromconnection.stateas fallbackpackages/agents/src/index.ts:1388-1394—isConnectionProtocolEnablednow reads fromconnection.stateas fallbackcheckProtocolEnabledAfterCacheClearmethod that simulates the post-hibernation condition by clearing the WeakMap entry and restoring the rawstategetterCloses #881
github run