cloudflare / cloudflare/agents

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

Open
#2,118 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

**Describe the bug**

If a parent agent rejects a sub-agent WebSocket connection (e.g. `onBeforeSubAgent` returns a 404 for a deleted or unknown name), the client never finds out and retries forever.

The rejection happens before the upgrade, so the client sees a failed HTTP handshake instead of a WebSocket close frame. partysocket turns that into a synthesized close event with code 1000. `isTerminalCloseEvent` only treats 1008 and 4000-4999 as final (`packages/agents/src/client.ts:32-34`), so the client retries with `maxRetries = Infinity` and `connectionError` is never set. The app has no way to show an error.

The one code path that does send a final close, `close(1008, "Sub-agent connection rejected")` at `packages/agents/src/index.ts:7412`, only runs for nested sub-agents (`gate: true`). A direct parent to child rejection can never reach it, because the `Response` from `onBeforeSubAgent` is returned before the upgrade (`index.ts:7024-7028`).

**To Reproduce**

Live demo (open and watch the counters): https://agents-2118-repro.chakrabortyurjit.workers.dev

Or locally, no Cloudflare account needed:

1. `git clone https://github.com/urjitc/agents-2118-repro && cd agents-2118-repro`
2. `npm install`
3. `node repro.mjs` (starts a server that rejects upgrades with 404 and points partysocket at it)
4. Observed output:

```
tcp connections reaching server: 21
rejected upgrade attempts in 5s: 21
client error messages: [ 'Unexpected server response: 404' ]
close events seen by client: 21
distinct close codes: [ 1000 ]
any close classified terminal: false
client retryCount: 21
verdict: retries forever, nothing terminal ever surfaces
```

**Expected behavior**

After a rejected connection (or a few of them), the client should stop and set `connectionError` so the app can show an error state.

**Screenshots**

Not needed; the live demo shows the retry loop and counters directly.

**Version:**

`agents@0.19.0` (same behavior on main at `aed6d8f`), `partysocket@1.3.0`.

**Additional context**

Related to #2003 / #2024. Note that #2024 closes a deleted sub-agent's sockets with code 1001, which is also not treated as final, so those clients would land in this same retry loop.

Contributor guide

Open the contributing guide

Research direction

Start with packages/agents/src/client.ts:32-34 to trace terminal close classification, then read the rejection paths in packages/agents/src/index.ts:7024-7028 and the nested close path around 7412. Run node repro.mjs from the linked reproduction repository and verify that a rejected handshake stops retrying and sets connectionError instead of producing an endless retry loop.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.