# `Pixir.Tools.Bash`
[🔗](https://github.com/Ranvier-Technologies/pixir/blob/main/lib/pixir/tools/bash.ex#L1)

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
