fix(sdk-typescript): reuse the direct node token across reconnects - #441
Merged
Merged
Conversation
`AgentClient` passed the WebSocket client a token factory, so every connect attempt minted a fresh direct node token. Minting is not free: it upserts the agent's direct node and rotates its token, which places it on the workspace's shared write-admission lane alongside ordinary messaging. That made reconnects self-amplifying. A denied mint fails the connect, which schedules a reconnect, which mints again — so write backpressure generated more write load. Observed in production: an outdated client looping at 51 mints/minute drove its workspace's write lane to ~40% denial, which then blocked an unrelated agent/node bind. A node token is a long-lived credential with no expiry, so cache and reuse it. Reuse is bounded to three consecutive attempts that never reach a usable connection, so a token the server no longer accepts is still replaced; the counter resets once a connection opens and registers, which means a healthy client mints once per session. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Follow-up to #440. That PR stopped a transient admission denial from permanently breaking an agent bind; this one addresses why the workspace was saturated in the first place.
Cause
AgentClient.connect()hands the WebSocket client a token factory:So every connect attempt mints a fresh direct node token. That call is not free — it upserts the agent's direct node and rotates its token, which correctly places
POST /v1/agent/node-tokenon the workspace's shared write-admission lane, alongside ordinary messaging and only two concurrent leases.This makes reconnects self-amplifying. A denied mint rejects
openSocket(), which schedules a reconnect, which mints again. Write backpressure generates more write load.A production tail of
relaycast-cloud-apicaught it happening. In one 70-second window, a single workspace produced 47node-tokencalls — 51/minute, median 329ms apart — every one of them from@relaycast/sdk@5.0.5against an 8.11.0 engine. Twenty were denied. That load pushed the workspace's write lane to roughly 40% denial, which is what blocked an unrelatedPOST /v1/nodes/{name}/agentsand left a freshly spawned agent unbound.Backoff does not rescue this:
handleOpenresetsreconnectAttempt = 0as soon as a socket opens, so a connection that opens and then drops restarts at the base delay every cycle.Fix
A node token is a long-lived credential — the
nodestable stores atoken_hashwith no expiry column — so it is meant to be held and reused, not re-minted per connect.The token is now cached and reused across attempts. Reuse is bounded to three consecutive attempts that never reach a usable connection, so a token the server no longer accepts is still replaced rather than wedging the client. The counter resets once a connection actually opens and registers, which means a healthy client mints once per session no matter how often it reconnects.
The reset hangs off the
openevent, which the WS client emits only after node registration is sent. So the token is marked good when the connection is genuinely usable, not merely when the socket handshake completes.Tests
Two new cases in
agent-ws.test.ts: attempts that never open reuse the cached token and then re-mint once reuse is exhausted, and a connection that opens and registers repeatedly keeps a single minted token.474 tests pass,
tsc --noEmitis clean, and eslint reports no errors.Operational note
The
@relaycast/sdk@5.0.5client driving this is pinned incloud/package.jsonandcloud-canary/package.json, three majors behind the current 8.11.0. This fix reduces the blast radius of such a client, but upgrading it is the immediate relief.Made with Cursor