Failure scenario
- Start a plan containing a shell step such as
sh -c 'sleep 60; perform_mutation'.
- Cancel the plan task while the command is sleeping (for example because its caller disconnected or a future step deadline cancels it).
- Observe the command after cancellation.
Cancellation interrupts proc.communicate() but there is no cleanup for the subprocess or its descendants. The shell continues running independently and performs the mutation after the plan has already been cancelled. A direct reproduction cancelling ShellTool.execute() after 100 ms still observed its delayed child write a file one second later.
Site
src/odin/tools/shell.py:19-36 starts a subprocess and awaits it without a cancellation handler, owned process group, termination, or reaping path.
Expected result
Run shell steps in an owned process group and, on cancellation, terminate the complete group, wait for/reap it, escalate within a bound if needed, and only then propagate cancellation.
Failure scenario
sh -c 'sleep 60; perform_mutation'.Cancellation interrupts
proc.communicate()but there is no cleanup for the subprocess or its descendants. The shell continues running independently and performs the mutation after the plan has already been cancelled. A direct reproduction cancellingShellTool.execute()after 100 ms still observed its delayed child write a file one second later.Site
src/odin/tools/shell.py:19-36starts a subprocess and awaits it without a cancellation handler, owned process group, termination, or reaping path.Expected result
Run shell steps in an owned process group and, on cancellation, terminate the complete group, wait for/reap it, escalate within a bound if needed, and only then propagate cancellation.