This commit is contained in:
2026-05-14 20:08:09 +01:00
parent 58dbb56937
commit 27361f79c4
3 changed files with 130 additions and 2 deletions

View File

@@ -323,6 +323,54 @@ func (s *Session) reapChild(c *Child) {
c.markExited(err)
logf("child %s exited (err=%v)", c.ID, err)
s.emitExit(c)
s.killDescendantsOf(c.ID)
}
// killDescendantsOf terminates every still-live direct child of
// parentID. SPEC §2: closing the patterm process tears down every
// child it spawned; the same rule applies in-session — when an
// orchestrator dies (natural exit, user Ctrl-C, MCP close, anything
// that makes its PTY EOF), the agents/commands/terminals it spawned
// must die with it. We only signal direct children here: each
// descendant's own reapChild will fire and recurse, so the cascade
// flows through arbitrary depth without us walking the tree.
func (s *Session) killDescendantsOf(parentID string) {
if parentID == "" {
return
}
s.mu.Lock()
var live []*Child
for _, c := range s.children {
if c.ParentID == parentID && c.IsLive() {
live = append(live, c)
}
}
s.mu.Unlock()
if len(live) == 0 {
return
}
for _, c := range live {
_ = c.signal(syscall.SIGTERM)
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
anyLive := false
for _, c := range live {
if c.IsLive() {
anyLive = true
break
}
}
if !anyLive {
return
}
time.Sleep(20 * time.Millisecond)
}
for _, c := range live {
if c.IsLive() {
_ = c.signal(syscall.SIGKILL)
}
}
}
// Children returns a snapshot of children in spawn order.