framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
53 lines • 2.11 kB
JavaScript
/**
* Process-tree kill registry.
*
* The framework spawns the Claude Code CLI, which in turn spawns a deep subtree
* (node workers, ripgrep, bash tool calls, MCP servers). Signaling only the
* top-level `claude` orphans that subtree: it reparents to init and keeps
* burning CPU — the runaway-process leak we hit (a swarm of stray `claude`
* processes after an agent was interrupted).
*
* Fix: spawn each long-lived child as its own process-group leader (`detached`)
* and signal the whole group at once via a negative pid. Register every live
* child here so a hard framework exit (crash, uncaught error, process.exit)
* still reaps the trees on the way out.
*/
/** Live process-group leaders we spawned, by pid. Force-killed on our exit. */
const live = new Set();
let exitHookInstalled = false;
/**
* Signal an entire process group. `pid` must be a group leader (spawned
* `detached`); the negative pid targets the group, reaping the whole subtree in
* one shot. Never throws — a group that already exited is not an error.
*/
export function killTree(pid, signal) {
try {
process.kill(-pid, signal);
}
catch {
// Group already gone, or pid never led one; nothing to do.
}
}
/** Track a detached child so it is force-killed if the framework exits first. */
export function registerChild(pid) {
installExitHook();
live.add(pid);
}
/** Stop tracking a child that has already exited (or been killed). */
export function unregisterChild(pid) {
live.delete(pid);
}
function installExitHook() {
if (exitHookInstalled)
return;
exitHookInstalled = true;
// `exit` fires on normal completion, process.exit(), and after an uncaught
// error unwinds — sync-only, so a plain group SIGKILL is all we can (and need
// to) do. Signal deaths (SIGINT/SIGTERM) are handled by the CLI, which aborts
// the agent first; this is the last-resort net for every other exit path.
process.on('exit', () => {
for (const pid of live)
killTree(pid, 'SIGKILL');
});
}
//# sourceMappingURL=child-registry.js.map