cloudflare / cloudflare/agents
Rejected sub-agent WebSocket connection retries forever, no error surfaced
- 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
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