overengineeringstudio / overengineeringstudio/effect-utils
Design and implement Effect-based process group management
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 82
- Forks
- 2
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 121
Description
Problem
When spawning processes via @effect/platform/Command, child processes can leak and survive parent termination. This is especially problematic for:
- Shell commands (
runInShell: true) that spawn their own children - Long-running services (dev servers, workers) that need guaranteed cleanup
- Complex deployments with nested process trees
Currently our cmd.ts only kills the immediate process via proc.kill(), leaving descendants orphaned (PPID=1) and potentially holding resources like TCP ports.
Root Cause
The core issue is that Node.js child_process.kill() only signals the immediate child, not its descendants. To kill an entire process tree, you need:
Unix: Spawn with detached: true (creates new process group where PGID = child PID), then process.kill(-PGID, signal) to signal the entire group
Windows: Use taskkill /pid ${pid} /T /F where /T kills the tree
Related Effect Issues
- Effect #5303 (Open) - "Clean up the entire Command's child process group by default" - proposes
detached: trueby default - Effect #5592 (Closed) - "Command processes survive SIGINT when parent exits" - describes the orphaned process problem
Proposed Solution
Create a well-designed Effect library that captures the complexity of process group management:
Core ProcessGroup Service
export class ProcessGroup extends Effect.Service<ProcessGroup>()(
'ProcessGroup',
{
scoped: Effect.gen(function* () {
const processes = new Map<number, ManagedProcess>()
const spawn = (cmd: Command.Command) => // Spawn with detached: true, track process
const killAll = (signal?: NodeJS.Signals) => // SIGTERM → wait → SIGKILL escalation
const list = () => // List active processes
// Auto-cleanup on scope exit
yield* Effect.addFinalizer(() => killAll().pipe(Effect.ignore))
return { spawn, killAll, list } as const
}),
}
) {}
Key Features
- Automatic process group creation - Spawn with
detached: trueon Unix - SIGTERM → SIGKILL escalation - Graceful shutdown with configurable timeout
- Platform-specific kill - Negative PID on Unix,
taskkill /Ton Windows - Scoped cleanup - All processes killed when Effect scope closes
- setsid integration - Optional new session for complete isolation
Advanced Features (Future)
- Supervisor pattern - Restart policies (
always,on-failure,never) with backoff - Health checks - HTTP/TCP/exec probes before considering process "ready"
- Process trees - Track parent-child relationships for complex orchestration
Implementation Plan
Phase 1: Core Process Group
-
killProcessGroup()- Platform-specific group termination -
spawnDetached()- Spawn as process group leader -
ProcessGroupservice withspawn,killAll,list - SIGTERM → SIGKILL escalation with configurable timeout
Phase 2: Enhanced cmd() Integration
- Add
killTimeoutMs,killProcessGroupoptions tocmd() - Proper cleanup in
runWithLogging() - Cleanup diagnostics logging
Phase 3: Advanced Features
- Process supervisor with restart policies
- Health check integration
- Integration with Effect's interrupt system
References
- Node.js child_process detached option
- Killing process families with Node
- superchild npm package - Reference implementation
- Node.js Issue #40438 - Discussion on native support
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading cmd.ts and tracing the runWithLogging() path to understand current process cleanup and cmd() options. Confirm the intended scope with maintainers, since the issue spans a ProcessGroup service, platform-specific termination, escalation, scoped cleanup, and later supervisor features. Done means the agreed phases are implemented with cleanup behavior verified on supported platforms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100