Pixir.Tools.Bash (pixir v0.1.15)

Copy Markdown View Source

Run a shell command with the Workspace as the working directory.

Before crossing into host process execution, execute/2 acquires a bounded host-command lease from Pixir.Tools.CommandBoundary (ADR 0027). This keeps OS process fanout separate from BEAM-local Subagent/Workflow fanout.

Runs via a Port so a hung command can be killed on timeout. A portable Perl wrapper makes the spawned OS pid a process-group leader before it execs bash; Pixir then signals the whole group with SIGTERM and escalates it to SIGKILL after a short grace period before closing the port. An unlinked reaper monitors the collecting process and performs the same group cleanup if that process is brutally killed before it can return. A final direct-child sweep is belt-and-suspenders cleanup. Pending port messages are drained after close so repeated timeouts do not dirty the caller mailbox. Deliberately double-forked daemons that leave the process group are a residual out of scope. The timeout is an open knob: an agent-supplied timeout_ms replaces context.bash_timeout_ms or config :pixir, :bash_timeout_ms (default 120s), but is always capped by bash_timeout_max_ms (default 600s). Host-command concurrency and queueing use host_commands config.

v0.1 safety confines the cwd and rejects shell tokens that visibly resolve outside the workspace — parent-directory references, absolute paths, home/env-home paths, and existing symlink-prefix escapes — before crossing the host boundary. Only RHS values of leading POSIX environment assignments before a simple command are ignored; literal path arguments, redirection targets, and non-leading NAME=VALUE values are still checked. The accepted residual vector VAR=/outside cmd $VAR can expand at runtime, because this is a conservative tripwire, not a full shell parser or sandbox. The permission gate (ADR 0006) is still the higher-level guard: under :ask, non-safe commands prompt; under :read_only they are refused.