Move PTY cleanup to localized error boundaries

Restore provisional PTY size when build-options fails and guard pre-allocated handle registration. This ensures cleanup happens at the point of error, not deferred to the general catch block.
This commit is contained in:
Jinjing
2026-08-24 13:36:18 -07:00
parent 59dc14ec2f
commit 09e004fab3
4 changed files with 12 additions and 6 deletions
+4 -1
View File
@@ -84,7 +84,10 @@ export async function persistPtyIpcSpawnCommit(ctx: PtyIpcSpawnState): Promise<{
})
}
if (ctx.preAllocatedHandle && !ctx.stablePaneOwner?.handle) {
ctx.deps.runtime?.registerPreAllocatedHandleForPty(ctx.result.id, ctx.preAllocatedHandle)
if (ctx.deps.runtime?.registerPreAllocatedHandleForPty) {
ctx.deps.runtime.registerPreAllocatedHandleForPty(ctx.result.id, ctx.preAllocatedHandle)
ctx.agentTeamsLeaderHandle = null
}
}
ptySizes.set(ctx.result.id, { cols: args.cols, rows: args.rows })
if (ctx.effectiveSessionAppId !== undefined && ctx.effectiveSessionAppId !== ctx.result.id) {
-1
View File
@@ -25,7 +25,6 @@ import { persistPtyIpcSpawnCommit } from './spawn-commit-persist'
export async function commitPtyIpcSpawn(ctx: PtyIpcSpawnState): Promise<PtySpawnResult> {
const args = ctx.args
const { rendererPreSignaled, rendererAlreadyRegistered } = await persistPtyIpcSpawnCommit(ctx)
ctx.agentTeamsLeaderHandle = null
// Why: seed the headless emulator before registerPty so concurrent live PTY data lands on top of the seed, not replacing it (mobile keeps the daemon-restored scrollback).
// Skip when the renderer will be authoritative — its xterm buffer is richer than the daemon snapshot.
+4 -2
View File
@@ -38,7 +38,10 @@ export async function runPtyIpcSpawn(deps: PtySpawnIpcDeps, args: PtySpawnIpcArg
try {
await preparePtyIpcSpawnPreflight(ctx)
await assemblePtyIpcSpawnEnv(ctx)
const earlyReserved = await buildPtyIpcSpawnOptions(ctx)
const earlyReserved = await buildPtyIpcSpawnOptions(ctx).catch((error: unknown) => {
restoreProvisionalPtySize(ctx)
throw error
})
if (earlyReserved) {
// Why: this request lost the pane to the reservation winner, so its
// pre-allocated leader handle never binds to a PTY. Nothing else can
@@ -51,7 +54,6 @@ export async function runPtyIpcSpawn(deps: PtySpawnIpcDeps, args: PtySpawnIpcArg
return await commitPtyIpcSpawn(ctx)
} catch (err) {
releaseAbandonedAgentTeamsLeader(ctx)
restoreProvisionalPtySize(ctx)
if (ctx.preSpawnHiddenMarkId !== null) {
ctx.deps.transitionSpawnHiddenRendererPtyDeliveryState(ctx.preSpawnHiddenMarkId, false)
}
+4 -2
View File
@@ -87,14 +87,16 @@ export async function spawnPtyFromRuntimeController(
if (earlyAdopt) {
return toRuntimeSpawnReply(earlyAdopt)
}
const earlyReserved = await buildRuntimePtySpawnOptions(ctx)
const earlyReserved = await buildRuntimePtySpawnOptions(ctx).catch((error: unknown) => {
restoreProvisionalPtySize(ctx)
throw error
})
if (earlyReserved) {
return toRuntimeSpawnReply(earlyReserved)
}
await executeRuntimePtySpawn(ctx)
return toRuntimeSpawnReply(await commitRuntimePtySpawn(ctx))
} catch (err) {
restoreProvisionalPtySize(ctx)
if (ctx.pendingRegistrationPtyId) {
deps.runtime?.cancelPendingPtyRegistration?.(
ctx.pendingRegistrationPtyId,