You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(sdk/ts): forward().close() never revokes SSH session tokens of open connections #4516
I use OpenShell and build automation against its SDKs. I found this while reviewing sdk/ on main; it is identified from the code paths cited below and has not yet been reproduced against a live gateway.
Problem Statement
On close() (or caller abort), teardown aborts each connection's AbortController. forwardConnection's finally then calls revokeSshSession({ token, allowMissing: true }, { signal }) with that same, already-aborted signal, so the revoke RPC is never sent. The Go file client revokes with a fresh background context.
Impact / Why This Matters
Every SSH session token minted for connections still open at close() stays valid until it expires instead of being revoked, contrary to the method's documented revoke-on-close behavior.
Acceptance Criteria
Closing a forward handle with open connections issues RevokeSshSession for each minted token (bounded by its own timeout, independent of the aborted connection signal).
Reproduction Steps
const h = await client.sandbox.forward(name, { targetPort }).
Open a local connection so a session token is minted.
await h.close().
Observe no RevokeSshSession RPC reaches the gateway for that token (the call rejects immediately on the aborted signal).
Confirmed by code inspection on main. teardown in sdk/typescript/src/client.ts aborts each connection's AbortController, and forwardConnection's finally block then calls revokeSshSession({ token, allowMissing: true }, { signal }) with that same aborted signal, so the request is rejected before it is sent. The surrounding catch swallows the error. Connections that end normally are revoked correctly, since their signal was never aborted. Only connections still open at close() or caller abort keep a live token until it expires.
The Go SDK handles this correctly in both its SSH tunnel (revokeSessionForCleanup) and its file client, by revoking with a fresh background context and a short timeout. The TypeScript fix can do the same, for example with AbortSignal.timeout(...) instead of the connection signal. A unit test that closes a forward handle with an open connection and asserts that RevokeSshSession reaches the mock server would lock this in.
The acceptance criterion is clear. Next step: maintainer acceptance decision.
Activity
rhuss commentedon Oct 16, 2026
Thanks for the report.
Confirmed by code inspection on
main.teardowninsdk/typescript/src/client.tsaborts each connection'sAbortController, andforwardConnection'sfinallyblock then callsrevokeSshSession({ token, allowMissing: true }, { signal })with that same aborted signal, so the request is rejected before it is sent. The surroundingcatchswallows the error. Connections that end normally are revoked correctly, since their signal was never aborted. Only connections still open atclose()or caller abort keep a live token until it expires.The Go SDK handles this correctly in both its SSH tunnel (
revokeSessionForCleanup) and its file client, by revoking with a fresh background context and a short timeout. The TypeScript fix can do the same, for example withAbortSignal.timeout(...)instead of the connection signal. A unit test that closes a forward handle with an open connection and asserts thatRevokeSshSessionreaches the mock server would lock this in.The acceptance criterion is clear. Next step: maintainer acceptance decision.