Add session shutdown method across all SDKs#682
Conversation
Add a new shutdown() method (ShutdownAsync in .NET, Shutdown in Go) that sends the session.destroy RPC to the CLI without clearing event handlers. This allows callers to observe the session.shutdown notification that the CLI sends after responding to the destroy request. The existing destroy() / DisposeAsync() method now calls shutdown() internally before clearing handlers, preserving full backward compatibility. Updated E2E tests in all four SDKs to exercise the new method and assert that the session.shutdown event is received. Updated API reference docs in all language READMEs, docs/compatibility.md, and the docs-maintenance agent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a shutdown() method (in language-appropriate naming) to all four SDKs — Node.js, Python, Go, and .NET — that sends the session.destroy JSON-RPC request to the server without clearing local event handlers. This enables callers to observe the server-sent session.shutdown notification before finalizing cleanup. destroy()/DisposeAsync() now delegates to shutdown() first, maintaining full backward compatibility for existing callers.
Changes:
- New
shutdown()/Shutdown()/ShutdownAsync()method in each SDK with idempotency guards. - Refactored
destroy()to callshutdown()internally then clear handlers as before. - E2E tests updated to call
shutdown(), await thesession.shutdownevent, then calldestroy(). Documentation updated in all language READMEs,docs/compatibility.md, and the docs-maintenance agent.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
nodejs/src/session.ts |
Adds shutdown() with _isShutdown bool guard; destroy() delegates to it |
nodejs/test/e2e/session.test.ts |
E2E test awaits session.shutdown event before calling destroy() |
python/copilot/session.py |
Adds shutdown() with _is_shutdown bool flag; destroy() delegates to it |
python/e2e/test_session.py |
E2E test awaits shutdown_event before calling destroy() |
go/session.go |
Adds Shutdown() using atomic.Bool; Destroy() delegates to it (error message changed) |
go/internal/e2e/session_test.go |
E2E test awaits session.shutdown event via channel before calling Destroy() |
dotnet/src/Session.cs |
Adds ShutdownAsync() with Interlocked.Exchange guard; DisposeAsync() delegates to it |
dotnet/test/SessionTests.cs |
E2E test awaits shutdownReceived task before calling DisposeAsync() |
nodejs/README.md |
Documents new shutdown() method and updated destroy() description |
go/README.md |
Documents new Shutdown() method |
dotnet/README.md |
Documents new ShutdownAsync() method and updated DisposeAsync() description |
docs/compatibility.md |
Adds "Shutdown session" entry to the compatibility table |
.github/agents/docs-maintenance.agent.md |
Updates all four SDK method lists to include the new shutdown method |
|
Actually, I'm pretty sure the CLI isn't ever actually sending the shutdown event as part of session destroy. In which case the changes here are necessary but insufficient. |
✅ Cross-SDK Consistency ReviewI've reviewed this PR for consistency across all four SDK implementations, and I'm pleased to report excellent consistency throughout. The Summary of ChangesThis PR adds a new Consistency Analysis✅ Method Naming - Perfectly ConsistentAll SDKs follow proper language conventions:
✅ Core Behavior - Identical Across All SDKs
✅ Documentation - Consistent StructureAll four implementations include:
✅ README Updates - Complete Coverage
Note: Python README follows a different documentation pattern (minimal API reference) compared to other SDKs. This is pre-existing and not introduced by this PR. ✅ E2E Tests - Parallel ImplementationAll four E2E test suites now include the same pattern: ✅ Compatibility Documentation - UpdatedThe ✅ Docs-Maintenance Agent - UpdatedThe validation checklist in RecommendationsNo changes needed - This PR demonstrates exemplary cross-SDK consistency. The implementation:
The only minor observation is that Python's README doesn't have the same detailed method reference as the other SDKs, but this is a pre-existing pattern difference, not something introduced by this PR. Great work maintaining consistency across this multi-language SDK! 🎉
|
The CLI runtime does not currently emit session.shutdown notifications during the session.destroy flow, so remove E2E assertions that wait for the event. The shutdown() method and two-phase API remain in place for when the runtime is updated. Update the Node.js failed-cleanup test to reflect that shutdown()'s idempotency guard causes the destroy retry to succeed with local-only cleanup when the server process is dead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dc4ec90 to
1bc41cb
Compare
Problem
When a session is destroyed, the CLI sends a
session.shutdownnotification after responding to thesession.destroyRPC request. However, all four SDKs clear every event handler immediately upon receiving thesession.destroyresponse. By the time thesession.shutdownnotification arrives, there are no handlers left to receive it. The event is silently dropped.This means
session.shutdown— which is defined in the generated types and documented as part of the session lifecycle — is effectively unreceivable by any SDK consumer today.Alternatives considered
Remove handler clearing from destroy entirely. After destroy, the CLI sends no further events (shutdown is the last one), so clearing is not strictly necessary. However, all four SDKs document that
destroy()/DisposeAsync()clears handlers, and in .NET,DisposeAsynccarries the expectation that all work has quiesced upon return. Removing the clearing would break that contract as the handler for shutdown could run at any point afterDisposeAsync.DisposeAsynccould stall waiting for it, but then that's introducing potentially long waits into an operation that's supposed to be relatively quick.Add a timeout-based wait inside destroy. We could wait a short window (e.g. 500ms) for the shutdown event before clearing handlers. This is fragile — the delay is arbitrary, adds latency to every destroy call, and still provides no guarantee the event arrives in time.
Solution
Introduce a new shutdown method in each SDK:
session.shutdown()await session.shutdown()session.Shutdown(ctx)await session.ShutdownAsync()shutdown()sends thesession.destroyRPC to the CLI but does not clear event handlers. This gives the caller an explicit window to observe thesession.shutdownnotification before they choose to finalize cleanup.destroy()/DisposeAsync()now callsshutdown()internally, then clears handlers as before. Callers who never callshutdown()directly see no behavior change — full backward compatibility is preserved.Both methods are idempotent: calling
shutdown()twice is a no-op on the second call, anddestroy()safely handles the case whereshutdown()was already called.Typical usage pattern
What's included
session.shutdownevent is received, then calls destroydocs/compatibility.md, and the docs-maintenance agent