fix(pty): let agents run exit hooks before an immediate local-provider close SIGKILLs them (#33367)

Local (in-process) PTY provider: an immediate close now SIGTERMs descendants and waits up to the shutdown grace before SIGKILL (same as the daemon path since #22232), so Claude's SessionEnd hooks run. Refs #23865, #22039. Supersedes #24314.

Co-authored-by: innocarpe <2222333+innocarpe@users.noreply.github.com>
This commit is contained in:
Neil
2026-10-24 20:09:42 -07:00
committed by GitHub
co-authored by innocarpe
parent 25b9ce5168
commit fd240408b4
2 changed files with 7 additions and 1 deletions
@@ -523,6 +523,8 @@ describe('LocalPtyProvider', () => {
expect.any(Function),
expect.objectContaining({ ownsRoot: expect.any(Function) })
)
// Root group SIGKILL must wait for the descendant SIGTERM grace, or Claude skips SessionEnd.
expect(killWithDescendantSweepMock.mock.calls[0][2].awaitEscalation()).toBe(true)
})
})
+5 -1
View File
@@ -1,6 +1,7 @@
import type * as pty from 'node-pty'
import { PhysicalExitTracker } from '../../shared/physical-exit-tracker'
import { killWithDescendantSweep } from '../pty-descendant-termination'
import { terminateShutdownDescendants } from '../daemon/terminal-descendant-shutdown'
import { forceKillPosixPtyProcessGroups } from '../pty/posix-pty-process-groups'
import { terminatePtyJob } from '../windows/windows-pty-job'
import {
@@ -179,7 +180,10 @@ async function shutdownTrackedPty(
// Typed agents also detach tool process groups; immediate close must snapshot before root exit.
await killWithDescendantSweep(proc.pid, signalRoot, {
ownsRoot: () => ptyProcesses.get(id) === proc,
terminateOwnedTree: () => terminatePtyJob(proc)
terminateOwnedTree: () => terminatePtyJob(proc),
terminateDescendants: terminateShutdownDescendants,
// Group SIGKILL must wait out the SIGTERM grace so agents can run exit hooks (Claude SessionEnd).
awaitEscalation: () => operation.immediate
})
} else {
signalRoot()