Skip to main content

Module process

Module process 

Source
Expand description

Process / shell integration.

(exec-check CMD ARG…) → 0 on success, non-zero exit code otherwise Streams stdin/stdout/stderr to the parent. (exec-capture CMD ARG…) → the CAPTURE RECORD (below) Captures stdout + stderr, exposes exit code. (exec-ok? CMD ARG…) → bool; true iff exit code is 0 (sh-exec STR) → convenience: run STR through sh -c returning the capture-form result

Credential-carrying variants. Use these instead of putting a secret in an argument: argv is world-readable from the process table, and in CI it is readable by co-tenant steps and sibling containers.

(exec-with-stdin IN CMD ARG…) → capture form; IN is written to the child’s stdin. The --password-stdin shape that docker / helm / skopeo / gh already support. Preferred. (exec-with-env ENV CMD ARG…) → capture form; ENV is an alist of (KEY VALUE) pairs set for the child only. For tools with no stdin form. Weaker than stdin — the environment is readable by the same uid — but far stronger than argv.

No implicit shell interpolation. Arguments are passed literally to the underlying process; no glob / word-splitting / $VAR substitution. Scripts that want shell features use sh-exec explicitly.

── THE CAPTURE RECORD ─────────────────────────────────────────────────── Every capture-form primitive above returns the SAME eight-field alist, so a caller never has to know which form produced it:

(:status N) exit code, or -1 when killed by a signal (:stdout “…”) captured stdout (:stderr “…”) captured stderr (:argv (“cmd” “a”)) the argv LIST, exact and unsplit (:program “cmd”) the program as ASKED FOR (:resolved “/nix/…”) the program as RESOLVED — “” when PATH lookup failed (:cwd “/…”) the directory the child inherited (:duration-ms N) wall-clock milliseconds

The last five landed 2026-08-17 and are not decoration. A deshellify port’s correctness argument is always “the new thing does what the old thing did”, which is a COMPARISON — and you cannot compare an invocation you did not record. Concretely: :resolved answers “did the RIGHT tool run” (the silent-PATH-fallback class — Command::new("kubectl") runs whatever PATH found first, and 218 such bare spawns were measured in pleme-io/forge); :cwd distinguishes a write to a flake’s read-only /nix/store source copy from one to the work tree, which otherwise surfaces only as a bare Permission denied (os error 13); :duration-ms separates “failed” from “hung”, which a status alone cannot.

:argv is a LIST and never a joined string, because re-quoting a command changes it — a single string cannot be compared against what was run.

ADDITIVE by construction: status-of / stdout-of / stderr-of and every other alist-get consumer are unaffected, and nothing in the tree asserted the record’s length.

Canonical technique: pleme-io/docs/controlled-subprocess.md (rung 2).

Functions§

install