Summary
TrackedProcess has no Drop implementation. If bugatti panics between spawn_long_lived_commands and teardown_processes, all child processes are orphaned. No process group is used either, so a SIGKILL to bugatti won't propagate to children.
Severity: S2 (Medium) | Confidence: 0.90 | Blast radius: Medium — orphaned long-lived services (databases, servers) hold ports and resources after a crash.
Technical Details
// src/command.rs:525
// teardown_processes is a manual function call, not a RAII guard
pub fn teardown_processes(processes: &mut [TrackedProcess]) { ... }
Current flow:
spawn_long_lived_commands() returns Vec<TrackedProcess>
- Test execution runs (may panic due to F2 or any other bug)
teardown_processes() is called manually in the happy path
- On panic:
teardown_processes() is never called → orphaned children
Evidence that this is a known gap: tests at command.rs:992 manually call teardown_processes in cleanup, confirming there's no automatic cleanup.
Additionally, child processes are spawned without setsid or setpgid, so they don't share a process group with bugatti. A kill(0, SIGTERM) from the OS won't reach them.
Proposed Fix
- Add
impl Drop for TrackedProcess that sends SIGTERM, waits briefly (1-2s), then SIGKILL
- Use a guard pattern: wrap
Vec<TrackedProcess> in a ProcessGuard struct with Drop
- Consider using
std::process::Command::process_group(0) or pre_exec with setsid so children share a process group
- Ensure
teardown_processes() marks processes as "already cleaned up" to avoid double-kill when Drop runs after explicit teardown
Estimated effort: ~4 hours
Related
- Related to F3: provider shutdown has no kill path
- Related to F2: panic in stream path triggers this scenario
- Part of refactor Bundle 2: Process Lifecycle Safety
🔍 Found by vibe-code-audit — automated codebase audit skill for Claude Code.
Summary
TrackedProcesshas noDropimplementation. If bugatti panics betweenspawn_long_lived_commandsandteardown_processes, all child processes are orphaned. No process group is used either, so a SIGKILL to bugatti won't propagate to children.Severity: S2 (Medium) | Confidence: 0.90 | Blast radius: Medium — orphaned long-lived services (databases, servers) hold ports and resources after a crash.
Technical Details
Current flow:
spawn_long_lived_commands()returnsVec<TrackedProcess>teardown_processes()is called manually in the happy pathteardown_processes()is never called → orphaned childrenEvidence that this is a known gap: tests at
command.rs:992manually callteardown_processesin cleanup, confirming there's no automatic cleanup.Additionally, child processes are spawned without
setsidorsetpgid, so they don't share a process group with bugatti. Akill(0, SIGTERM)from the OS won't reach them.Proposed Fix
impl Drop for TrackedProcessthat sends SIGTERM, waits briefly (1-2s), then SIGKILLVec<TrackedProcess>in aProcessGuardstruct withDropstd::process::Command::process_group(0)orpre_execwithsetsidso children share a process groupteardown_processes()marks processes as "already cleaned up" to avoid double-kill whenDropruns after explicit teardownEstimated effort: ~4 hours
Related