Skip to content

feat(env)!: scrub release variables from child processes - #170

Merged
joshrotenberg merged 2 commits into
mainfrom
feat/156-release-child-env
Nov 1, 2026
Merged

joshrotenberg merged 2 commits into
mainfrom
feat/156-release-child-env

Conversation

@joshrotenberg

@joshrotenberg joshrotenberg commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #156.

Changes

Commands launched from a Mix release inherited its boot variables and release runtime directories, causing a project's elixir and mix to fail with cannot get bootfile. Child environments now scrub these values by default when the parent has RELEASE_ROOT set.

  • Add Config.release_env, defaulting to :scrub, and route :inherit through convenience calls as an explicit opt-out.
  • Add pure ChildEnv.release_scrub/1 and normalize/3 helpers. Remove inherited RELEASE_*, BINDIR, and ROOTDIR, and complete PATH entries matching the parent's BINDIR, either root's bin, or either root's direct erts-<version>/bin.
  • Apply caller environment entries after removals, while also filtering explicit PATH values. Preserve explicit removals, unrelated PATH entries, ordering, duplicates, empty components, and replacement-environment semantics.
  • Read the parent environment when preparing execution, including lazy stream consumption. Share this behavior across command, session, raw, observed, and evidenced paths without changing runner process handling.
  • Document the default, opt-out, override ordering, and helper API in the README and changelog.

Acceptance

  • Every supported spawn path scrubs release variables and the specified complete PATH entries: Exec, ExecResume, Review, ExecFork, Session, all Commands modules, raw calls, execute and stream, on Port and Forcola.
  • Config.env applies after removals and may explicitly restore names. Explicit PATH values still have release entries removed.
  • No scrub occurs when RELEASE_ROOT is absent or release_env is :inherit.
  • Pure unit tests use fabricated parent environments. Fake-CLI tests inspect actual child environments on every runner path, including fresh/resumed sessions and observed/evidenced callbacks.
  • README section and changelog entry describe the new default.

Validation

Before both pushes, ran the complete reproducible CI check matrix locally:

Environment Result
Elixir 1.19.7 / OTP 27.3.4.19 Dependencies, unused-dependency check, Hex audit, warnings-as-errors compile, formatting, strict Credo, Dialyzer, and full tests passed: 736 tests, 0 failures (4 excluded)
Elixir 1.20.5 / OTP 29.1.2 Same full check set passed, including the full test suite (4 excluded)
Elixir 1.19 / OTP 27 docs mix docs --warnings-as-errors passed
Node 22.23.4 CLI contracts Native CLI 0.149.0, 0.164.0, and latest 0.166.0 passed without inference
Native macOS focused tests Pure helper and fake-CLI environment tests passed on Elixir 1.20.4 / OTP 29

The new fake-CLI suite has 18 tests covering 103 subprocess invocations. The real Forcola shim was available and exercised.

A real Mix release smoke test passed all eight combinations of runner (Port/Forcola), operation (execute/stream), and policy (scrub/inherit). With the default scrub, child elixir -e 'IO.puts(:ok)' and mix --version succeeded from an unrelated directory, with all 15 host RELEASE_* variables, BINDIR, ROOTDIR, and release PATH entries absent. With :inherit, both commands reproduced the original boot-file failure.

Four authenticated live Codex integration tests remain excluded by the repository's standard CI test command. Release publishing is a main-only side effect and was not run. No acceptance items remain incomplete.

GitHub CI passed all five jobs on 352ffb49527e1863d4da1aa8fb6109f08edaf742: both Elixir/OTP jobs, docs, and both pinned CLI contracts. Final CI run.

@joshrotenberg
joshrotenberg marked this pull request as ready for review November 1, 2026 13:52
@joshrotenberg
joshrotenberg merged commit 590ef0b into main Nov 1, 2026
5 checks passed
@joshrotenberg joshrotenberg changed the title feat(env): scrub release variables from child processes feat(env)!: scrub release variables from child processes Nov 1, 2026
joshrotenberg added a commit that referenced this pull request Nov 1, 2026
BREAKING CHANGE: Child processes launched inside an Elixir release, when RELEASE_ROOT is set, now scrub RELEASE_*, BINDIR, ROOTDIR, and release PATH entries by default, as introduced in #170. Set release_env: :inherit to opt out. Custom runners must honour {name, false} entries in opts[:env] by removing those variables from the inherited environment on every implemented execution callback.
joshrotenberg added a commit that referenced this pull request Nov 2, 2026
* fix(env): route release_env through IEx chat and resume

Route release_env through config for chat and resume so later say calls
retain the policy. Test inherited and scrubbed child variables and PATH.
Document false environment values in the runner typedoc and the
CHANGELOG sentence for the release environment scrub.

* fix(env)!: declare the release environment scrub from #170 as breaking

BREAKING CHANGE: Child processes launched inside an Elixir release, when RELEASE_ROOT is set, now scrub RELEASE_*, BINDIR, ROOTDIR, and release PATH entries by default, as introduced in #170. Set release_env: :inherit to opt out. Custom runners must honour {name, false} entries in opts[:env] by removing those variables from the inherited environment on every implemented execution callback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(env): scrub release boot variables and PATH entries from the codex child

1 participant