diff --git a/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts b/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts index 688db06c4..32ee56455 100644 --- a/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +++ b/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts @@ -16,6 +16,7 @@ import { import { processes, pendingAlerts, + pushAlert, cleanupAll, cleanupSessionProcesses, persistManifest, @@ -37,19 +38,30 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS } } - // Clean up on session shutdown - pi.on("session_shutdown", async () => { - cleanupAll(); - }); - // Register signal handlers to clean up bg processes on unexpected exit (fixes #428) const signalCleanup = () => { cleanupAll(); + // Also kill bash-tool spawned children that bg-shell doesn't track + try { + const { listDescendants } = require("@gsd/native") as typeof import("@gsd/native"); + const descendants = listDescendants(process.pid); + for (const childPid of descendants) { + try { process.kill(childPid, "SIGKILL"); } catch {} + } + } catch {} }; process.on("SIGTERM", signalCleanup); process.on("SIGINT", signalCleanup); process.on("beforeExit", signalCleanup); + // Clean up on session shutdown — remove signal handlers to prevent accumulation + pi.on("session_shutdown", async () => { + process.off("SIGTERM", signalCleanup); + process.off("SIGINT", signalCleanup); + process.off("beforeExit", signalCleanup); + cleanupAll(); + }); + // ── Compaction Awareness: Survive Context Resets ─────────────── /** Build a compact state summary of all alive processes for context re-injection */ @@ -65,7 +77,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS return ` - id:${p.id} "${p.label}" [${p.processType}] status:${p.status} uptime:${formatUptime(Date.now() - p.startedAt)}${portInfo}${urlInfo}${errInfo}${groupInfo}`; }).join("\n"); - pendingAlerts.push( + pushAlert(null, `${reason} ${alive.length} background process(es) are still running:\n${processSummaries}\nUse bg_shell digest/output/kill with these IDs.` ); } @@ -150,7 +162,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS ` - ${s.id}: ${s.label} (pid ${s.pid}, type: ${s.processType}${s.group ? `, group: ${s.group}` : ""})` ).join("\n"); - pendingAlerts.push( + pushAlert(null, `${surviving.length} background process(es) from previous session still running:\n${summary}\n Note: These processes are outside bg_shell's control. Kill them manually if needed.` ); } diff --git a/src/resources/extensions/bg-shell/process-manager.ts b/src/resources/extensions/bg-shell/process-manager.ts index db707fb40..659f13e26 100644 --- a/src/resources/extensions/bg-shell/process-manager.ts +++ b/src/resources/extensions/bg-shell/process-manager.ts @@ -33,6 +33,8 @@ export const processes = new Map(); /** Pending alerts to inject into the next agent context */ export let pendingAlerts: string[] = []; +const MAX_PENDING_ALERTS = 50; + /** Replace the pendingAlerts array (used by the extension entry point) */ export function setPendingAlerts(alerts: string[]): void { pendingAlerts = alerts; @@ -58,8 +60,12 @@ export function addEvent(bg: BgProcess, event: Omit): } } -export function pushAlert(bg: BgProcess, message: string): void { - pendingAlerts.push(`[bg:${bg.id} ${bg.label}] ${message}`); +export function pushAlert(bg: BgProcess | null, message: string): void { + const prefix = bg ? `[bg:${bg.id} ${bg.label}] ` : ""; + pendingAlerts.push(`${prefix}${message}`); + if (pendingAlerts.length > MAX_PENDING_ALERTS) { + pendingAlerts.splice(0, pendingAlerts.length - MAX_PENDING_ALERTS); + } } export function getInfo(p: BgProcess): BgProcessInfo {