feat(bindings): host env and mounts applied after construction survive reset - #2292
Merged
Conversation
…e reset Adds `Bash::set_env()` to the core as the environment counterpart to live `mount()`, and surfaces it as `setEnv` (NAPI) and `set_env` (Python). Both bindings now record host mutations applied after construction — `set_env` and the runtime `mount*` APIs — and replay them on every rebuild, so `reset()` preserves them the way it already preserved custom builtins and constructor files (TM-ISO-025). `unmount()` retracts its record, so a rebuild cannot resurrect a removed mount. Env a *script* exported stays transient. This makes a reusable mount + env + builtin bundle installable on a live instance instead of requiring a two-phase construct-then-apply split where only half the setup survives a reset (issue #2291). `Bash::set_env()` writes both the exported entry and the shell variable, matching `BashBuilder::env`: the exported entry alone is shadowed by a same-named shell variable during expansion, so a host value applied later would silently lose to a builder-configured one. Adds the `runtime_env` capability row to the parity contract, with reasons for the surfaces that intentionally lack it (Rust BashTool/ScriptedTool build a fresh shell per execution; CLI, browser wasm, and C ABI expose no post-construction host API).
Review follow-ups on the runtime mutation logs: - One entry per key in the env log. A host calling `setEnv()` per request accumulated an entry per call; replacing the key's entry keeps last-write-wins semantics and bounds the log by distinct key count. - The Python log helpers return `PyResult` instead of silently skipping a record on a poisoned lock, matching how the rest of the binding treats lock failures. Swallowing it would leave a caller believing a value survives `reset()`. - Document live `set_env` alongside live mounts in the mount guide, including the binding-level replay contract.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 9e37c43 | Commit Preview URL Branch Preview URL |
Aug 13 2026, 01:09 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
A host can now apply environment to a running interpreter, and host setup applied after construction survives a rebuild.
Bash::set_env(key, value)— the environment counterpart to livemount(). Preserves shell state; no rebuild.setEnv/set_env, plus a recorded replay of hostset_envand runtimemountcalls.reset()now keeps them the way it already kept custom builtins and constructor files.unmount()retracts its record, so a rebuild cannot resurrect a removed mount. Env a script exported stays transient —reset()still discards it.The practical effect: a reusable mount + env + builtin bundle installs onto an existing instance in one step, instead of splitting into a config-fragment the host merges by hand plus a post-construction apply function where only half the setup survives a reset.
Bash::set_envwrites both the exported entry and the shell variable, matchingBashBuilder::env. The exported entry alone is shadowed by a same-named shell variable during expansion, so a host value applied later would silently lose to a builder-configured one. This was caught by a failing binding test, not by inspection.Why
Issue #2291 asks for a publishable extension object with
mount/env/commandhooks. Investigating it surfaced a smaller, more concrete gap underneath:mountwas runtime-callable butenvwas construction-only, so no single-phase install was expressible; and of the three parts of such a bundle, only builtins survivedreset().Fixing that makes the extension shape ordinary userland code, and fixes a lifecycle inconsistency that stood on its own regardless of the issue.
Scope: this is the prerequisite, not the whole issue. Still open from #2291: the declarative extension object itself (
extensions: [...]/installExtension), schema-backedctx.command()onBash(todayaddBuiltingives raw argv;help/discover/--dry-runlive only inScriptedTool), andonBeforeExecute.Before / After
The issue's example, run against the shipped API (
node, NAPI binding):Before — two phases, and the mount is gone after a reset:
After — one phase, intact across
reset():Python is byte-identical on the same script:
Risk
reset()now keeps hostsetEnv/runtime mounts where it previously dropped them — the direction TM-ISO-025 already requires for constructor config, and the reasonunmount()had to become retracting rather than live-only.setEnv/mount/unmountand read once per rebuild.setEnvper request does not accumulate entries.Checklist
runtime_env_tests.rs), 15 NAPI (runtime-extension.spec.ts), 14 Python (test_runtime_extension.py), covering live application, export visibility, precedence against builder/constructor env, reset replay, unmount retraction, and the full bundle end to endKnowledge updated:
runtime_envrow in the capability parity contract (with recorded reasons for the five surfaces that intentionally lack it), TM-ISO-025 evidence, the live-mount guide, the Python package doc, and the knowledge log.Part of #2291