Skip to content

fix(agents): close rejected sub-agent WebSockets instead of failing the handshake (#2118) - #2401

Merged
threepointone merged 2 commits into
fix/nested-subagent-websocketsfrom
fix/2118-rejected-subagent-socket
Sep 28, 2026
Merged

threepointone merged 2 commits into
fix/nested-subagent-websocketsfrom
fix/2118-rejected-subagent-socket

Conversation

@threepointone

@threepointone threepointone commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2118.

Problem

When onBeforeSubAgent rejected a WebSocket upgrade, the handshake failed with an HTTP status. Browsers hide that status from script, so clients saw a generic close and reconnected forever. Separately, useAgent and AgentClient never reported that reconnection had ended, even when it had: a terminal close code, a shouldReconnectOnClose that declined, or exhausted maxRetries.

Fix

Server:

  • A rejected upgrade is now accepted and immediately closed. A 4xx status maps to close code 4000 + status, which is terminal. Anything else maps to 1011, which is retryable. The reason is Sub-agent connection rejected (<status>).
  • Nested hops use the same mapping; before, they closed with 1008 whatever the status was.
  • A rejection response that already carries a webSocket is passed through untouched.

Client:

  • createCloseClassifier decides, in shouldReconnectOnClose, whether this close ends reconnection. That is the point where partysocket still exposes enough state to know. When reconnection ends, useAgent and AgentClient set connectionError and reject pending calls.
  • An explicit close() still reports nothing.

Edge cases:

  • partysocket calls shouldReconnectOnClose even after an explicit close(), and before it increments its retry count. The classifier reads shouldReconnect === false to tell an explicit close apart.
  • partysocket leaves shouldReconnect true when maxRetries runs out. The classifier compares the retry count with maxRetries itself.

Tests

  • Routing tests cover close codes 4404, 4401 and 1011, and a nested hop that expects 4403.
  • React tests cover a rejected sub-agent, maxRetries exhaustion, a shouldReconnectOnClose that declines, and an explicit close() that produces no error.

Docs: docs/agents/sub-agents.md has a close-code table.

Depends on #2400: before that fix, the nested rejection case hit the depth-limit bug and closed with 1011.

@changeset-bot

changeset-bot Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 64155b9

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

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

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

@threepointone
threepointone added this pull request to stack #2407 September 28, 2026 14:47
@agent-think

agent-think Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

🟢 agents import sizes: 1 entry point changed, no growth

Entry point Exports Largest gzip change Size now
🆕 agents/client 1 new — 16.4 KiB
Changed exports (1)
Import Gzip change Size now
🆕 agents/client#createCloseClassifier — 16.4 KiB
How this works

Each runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement.

Compared 3ffc90a3 → 64155b9d · workflow run · reported by agent-think[bot]

@threepointone
threepointone marked this pull request as ready for review September 28, 2026 14:49

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

Comment thread docs/agents/sub-agents.md
@threepointone
threepointone force-pushed the fix/2118-rejected-subagent-socket branch from 4c148e0 to 92de61d Compare September 28, 2026 15:46
@pkg-pr-new

pkg-pr-new Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2401

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2401

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2401

hono-agents

npm i https://pkg.pr.new/hono-agents@2401

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2401

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2401

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2401

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2401

commit: 64155b9

threepointone and others added 2 commits September 28, 2026 17:50
…he handshake (#2118)

onBeforeSubAgent rejections of an upgrade now accept and close the socket:
4xx maps to 4000+status (terminal), anything else to 1011 (retryable).
Browsers hide failed-handshake statuses, so clients used to reconnect forever.

useAgent and AgentClient now set connectionError whenever reconnection ends:
a terminal code, a declined shouldReconnectOnClose, or spent maxRetries.
An explicit close() still reports nothing.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s them

The #2118 client tests created a call, waited for the close to reject it,
and only then attached the assertion. The browser reported the rejection
as unhandled, which failed the suite although every test passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@threepointone
threepointone force-pushed the fix/2118-rejected-subagent-socket branch from 92de61d to 64155b9 Compare September 28, 2026 16:54
@threepointone
threepointone merged commit 218df4a into main Sep 28, 2026
25 of 31 checks passed
@threepointone
threepointone deleted the fix/2118-rejected-subagent-socket branch September 28, 2026 18:04
@github-actions github-actions Bot mentioned this pull request Oct 2, 2026
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.

Rejected sub-agent WebSocket connection retries forever, no error surfaced

1 participant