Skip to content

Go SDK: add Client.CreateCloudSession#1396

Closed
tclem wants to merge 2 commits into
tclem/verbose-memefrom
tclem/go-create-cloud-session
Closed

Go SDK: add Client.CreateCloudSession#1396
tclem wants to merge 2 commits into
tclem/verbose-memefrom
tclem/go-create-cloud-session

Conversation

@tclem
Copy link
Copy Markdown
Member

@tclem tclem commented May 23, 2026

Stacked on #1394 — will be retargeted to main when that merges.

Ports CreateCloudSession to the Go SDK, matching the contract established by the Rust (#1394) and TypeScript (#1395) implementations.

What changed

go/client.go

  • CreateSession now returns a clear error when config.Cloud != nil, pointing callers to CreateCloudSession
  • New CreateCloudSession(ctx, config) method: validates config (requires Cloud, rejects caller-supplied SessionID or Provider), omits sessionId from the session.create wire payload (runtime assigns it), registers the session under the returned id, then calls flushPendingForSession before releasing the routing guard
  • pendingRouting struct + beginPendingSessionRouting / flushPendingForSession / waitForSession helpers: a refcounted guard buffers session.event notifications (bounded drop-oldest, 128 per id) and parks inbound request handlers (userInput.request, exitPlanMode.request, autoModeSwitch.request, hooks.invoke, systemMessage.transform) while a cloud session.create is in flight
  • TOCTOU race fixed: both handleSessionEvent and waitForSession re-check c.sessions after acquiring pending.mu, so an event/request that races with flushPendingForSession is dispatched directly rather than buffered and abandoned
  • Waiter buffer is also bounded at 128 per session id; oldest waiter is rejected with a clear error on overflow

go/cloud_session_test.go (new)

9 unit tests covering: CreateSession cloud rejection, wire payload (asserts sessionId is omitted), caller SessionID rejection, caller Provider rejection, missing Cloud rejection, early-notification buffering and replay, inbound-request parking and unblocking, drop-oldest overflow, waiter rejection on dispose.

go/README.md — adds CreateCloudSession to the API reference and a new Cloud Sessions section.

Deviations from Rust/TS

  • sessionFs.* inbound requests (generated client-session API handlers) are not pending-buffered, matching the known limitation documented in the TS PR. In practice the runtime does not initiate sessionFs.* calls before the session.create response.
  • Context cancellation during the c.client.Request call is not supported (the jsonrpc2 client has no context-aware Request API), same limitation as TS.

  Generated via Copilot (Claude Sonnet 4.6) on behalf of @tclem

- CreateSession now rejects config.Cloud with an error pointing to
  CreateCloudSession
- CreateCloudSession validates config (requires Cloud, rejects
  caller-supplied SessionID and Provider), omits sessionId from the
  session.create wire payload so the runtime assigns one
- Pending-routing support: a refcounted beginPendingSessionRouting
  guard buffers session.event notifications (bounded drop-oldest, 128
  per id) and parks inbound request handlers (userInput.request,
  exitPlanMode.request, autoModeSwitch.request, hooks.invoke,
  systemMessage.transform) until the runtime-assigned session id is
  registered; waiters are rejected with a clear error if pending mode
  ends without registration
- TOCTOU race fixed: after acquiring pending.mu, both handleSessionEvent
  and waitForSession re-check c.sessions so a notification/request that
  races with flushPendingForSession is dispatched directly rather than
  buffered and abandoned
- Waiter buffer is also bounded at 128; oldest waiter is rejected on
  overflow
- README and inline godoc updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Carries the Rust SDK PR #1394 follow-up review fixes into the Go port:

1. Cap the per-session parked-waiter list at 128. When exceeded, reject
   the oldest waiter with errPendingSessionBufferOverflow ('pending
   session buffer overflow'). The handler returns a *jsonrpc2.Error with
   code -32603 via the new pendingRoutingRPCError helper, so the runtime
   receives a proper error response instead of hanging on the request id
   until its own timeout. Mirrors Rust commit 491b442 and TS commit
   c167bc3.

2. When the last pending-routing guard drops without RegisterSession
   (e.g. session.create failed mid-RPC), signal all parked waiters with
   errPendingSessionRoutingEnded ('pending session routing ended before
   session was registered'). Distinct phrasing from the overflow path so
   debugging can tell the two cases apart. Mirrors Rust commit e0ff254
   and TS commit c167bc3.

Adds pendingRoutingRPCError helper that routes sentinel errors to
-32603 while unknown-session errors keep -32602.

Adds two tests:
- TestPendingRouting_OverflowEmitsError: 129 parked waiters, oldest
  gets -32603 overflow error, remaining 128 resolve normally after
  registration.
- TestPendingRouting_GuardDropDistinctMessage: parks a request, drops
  the guard without registration, verifies exact routing-ended message
  and -32603 code.

Updates TestPendingRouting_RejectsWaitersOnDispose to assert the new
exact message and code instead of the old 'dropped' substring check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

The Go implementation in this PR looks great and correctly matches the Rust (#1394) and TypeScript (#1395) contracts. However, Python and .NET are not yet consistent with this new pattern.

Missing in Python (python/copilot/client.py)

Python currently exposes create_session(cloud=...) which passes the cloud option directly in the wire payload (no validation, no separate method). Per the contract established by Rust/TS/Go:

  1. create_session(cloud=...) should raise an error directing callers to the new method.
  2. A new create_cloud_session(config) method is needed that:
    • Requires cloud in config
    • Rejects caller-supplied session_id or provider
    • Omits session_id from the session.create wire payload (runtime assigns it)
    • Buffers early session.event notifications / parks inbound request handlers while the create is in flight

Missing in .NET (dotnet/src/Client.cs)

.NET currently exposes CreateSessionAsync(Cloud=...) with no validation. The same changes are needed:

  1. CreateSessionAsync should throw when config.Cloud != null, directing callers to the new method.
  2. A new CreateCloudSessionAsync(SessionConfig config, CancellationToken cancellationToken) method is needed with the same contract as above.

Not blocking this PR

Since this PR is part of a stacked series and Python/Java/Rust SDKs are being addressed separately, this is just a tracking note. It would be worth opening follow-up issues or PRs for Python and .NET to maintain feature parity across all four SDKs.

Generated by SDK Consistency Review Agent for issue #1396 · ● 4.8M ·

@tclem
Copy link
Copy Markdown
Member Author

tclem commented May 23, 2026

Consolidating into #1395 so the sdk-consistency-review workflow sees the full cross-SDK matrix in one PR. Commits from this branch are merged into that PR's branch.

  Generated via Copilot (Claude Opus 4.7) on behalf of @tclem

@tclem tclem closed this May 23, 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.

1 participant