From bdbd1d46a96428f8785fdb3adef2a131d1e9a99b Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Thu, 5 Mar 2026 17:53:58 -0800 Subject: [PATCH] fix: harden session lifecycle and align cli.mdx example with claude.json - destroySession: wrap session/cancel RPC in try/catch so local cleanup always succeeds even when the agent is unreachable - createSession/resumeOrCreateSession: clean up the remote session if post-creation config calls (setMode/setModel/setThoughtLevel) fail, preventing leaked orphan sessions - cli.mdx: fix example output to match current claude.json (model name, model order, and populated modes) Co-Authored-By: Claude Opus 4.6 --- docs/cli.mdx | 15 ++++++++-- sdks/typescript/src/client.ts | 56 ++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/docs/cli.mdx b/docs/cli.mdx index 580c560..fa6aa4e 100644 --- a/docs/cli.mdx +++ b/docs/cli.mdx @@ -200,13 +200,22 @@ Example output: "models": { "currentValue": "default", "values": [ - { "value": "default", "name": "Default (recommended)" }, - { "value": "opus", "name": "Opus" }, + { "value": "default", "name": "Default" }, { "value": "sonnet", "name": "Sonnet" }, + { "value": "opus", "name": "Opus" }, { "value": "haiku", "name": "Haiku" } ] }, - "modes": { "values": [] }, + "modes": { + "currentValue": "default", + "values": [ + { "value": "default", "name": "Default" }, + { "value": "acceptEdits", "name": "Accept Edits" }, + { "value": "plan", "name": "Plan" }, + { "value": "dontAsk", "name": "Don't Ask" }, + { "value": "bypassPermissions", "name": "Bypass Permissions" } + ] + }, "thoughtLevels": { "values": [] } } ] diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts index 9d32e71..6bbc544 100644 --- a/sdks/typescript/src/client.ts +++ b/sdks/typescript/src/client.ts @@ -679,14 +679,23 @@ export class SandboxAgent { live.bindSession(record.id, record.agentSessionId); let session = this.upsertSessionHandle(record); - if (request.mode) { - session = (await this.setSessionMode(session.id, request.mode)).session; - } - if (request.model) { - session = (await this.setSessionModel(session.id, request.model)).session; - } - if (request.thoughtLevel) { - session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session; + try { + if (request.mode) { + session = (await this.setSessionMode(session.id, request.mode)).session; + } + if (request.model) { + session = (await this.setSessionModel(session.id, request.model)).session; + } + if (request.thoughtLevel) { + session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session; + } + } catch (err) { + try { + await this.destroySession(session.id); + } catch { + // Best-effort cleanup + } + throw err; } return session; @@ -728,14 +737,23 @@ export class SandboxAgent { const existing = await this.persist.getSession(request.id); if (existing) { let session = await this.resumeSession(existing.id); - if (request.mode) { - session = (await this.setSessionMode(session.id, request.mode)).session; - } - if (request.model) { - session = (await this.setSessionModel(session.id, request.model)).session; - } - if (request.thoughtLevel) { - session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session; + try { + if (request.mode) { + session = (await this.setSessionMode(session.id, request.mode)).session; + } + if (request.model) { + session = (await this.setSessionModel(session.id, request.model)).session; + } + if (request.thoughtLevel) { + session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session; + } + } catch (err) { + try { + await this.destroySession(session.id); + } catch { + // Best-effort cleanup + } + throw err; } return session; } @@ -743,7 +761,11 @@ export class SandboxAgent { } async destroySession(id: string): Promise { - await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true); + try { + await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true); + } catch { + // Best-effort: agent may already be gone + } const existing = await this.requireSessionRecord(id); const updated: SessionRecord = {