Skip to content

Fix lost flags after hibernation wake#893

Closed
ask-bonk[bot] wants to merge 2 commits into
mainfrom
opencode/issue881-20260211205029
Closed

Fix lost flags after hibernation wake#893
ask-bonk[bot] wants to merge 2 commits into
mainfrom
opencode/issue881-20260211205029

Conversation

@ask-bonk

@ask-bonk ask-bonk Bot commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

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 _rawStateAccessors WeakMap 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:

  1. Runtime calls webSocketMessage(ws, msg) on the hibernated DO
  2. DO constructor runs (fresh _rawStateAccessors = new WeakMap())
  3. onStart() fires inside blockConcurrencyWhile():
    • mcp.restoreConnectionsFromStorage() runs
    • broadcastMcpServers() calls _broadcastProtocol()
  4. _broadcastProtocol() iterates all connections, calling isConnectionProtocolEnabled(conn) for each
  5. isConnectionProtocolEnabled checks _rawStateAccessors.get(connection)returns undefined because the WeakMap is empty
  6. The fallback returned true (protocol enabled), ignoring the _cf_no_protocol flag in the serialized attachment
  7. Result: cf_agent_mcp_servers is sent to all connections, including ones that opted out

This is why @genmon saw the rogue frames only when the DO woke after hibernation — the flag was correctly checked during normal operation (when _rawStateAccessors was populated), but the fallback path after hibernation wake was broken.

Fix

In both isConnectionProtocolEnabled and isConnectionReadonly, when _rawStateAccessors doesn't have the connection, we now fall back to reading connection.state directly. Before _ensureConnectionWrapped runs (which overrides the state getter to strip internal keys), partyserver's createLazyConnection defines state as a getter that returns the raw attachment — including internal flags like _cf_no_protocol.

Changes:

  • packages/agents/src/index.ts:1326-1332isConnectionReadonly now reads from connection.state as fallback
  • packages/agents/src/index.ts:1388-1394isConnectionProtocolEnabled now reads from connection.state as fallback
  • Test agent and test file updated with a new checkProtocolEnabledAfterCacheClear method that simulates the post-hibernation condition by clearing the WeakMap entry and restoring the raw state getter

Closes #881

github run

…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-bot

changeset-bot Bot commented Feb 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c70c7cf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
agents Major
@cloudflare/ai-chat Major
@cloudflare/codemode Major
hono-agents Major

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

@pkg-pr-new

pkg-pr-new Bot commented Feb 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@893

commit: c70c7cf

@threepointone
threepointone deleted the opencode/issue881-20260211205029 branch February 15, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: option to turn off agent protocol messages on the websocket

1 participant