User-defined env vars and PATH-prepends from the tool, configuration, and global Settings layers are merged into the child process before spawn — see the layering rules in the table below.
When ScripTree starts, it publishes these variables on its own
environment. They're inherited by every tool subprocess and are
also available as %VAR% / $VAR references inside tool
.scriptree fields (see "Path expansion" below):
| Variable | Value | Always set? |
|---|---|---|
SCRIPTREE_HOME |
The launcher directory (the folder holding run_scriptree.py) |
Yes |
SCRIPTREE_LIB |
<HOME>/lib |
Only if lib/ exists |
SCRIPTREE_LIB_PYPI |
<HOME>/lib/pypi (vendored Python packages) |
Only if lib/pypi/ exists |
SCRIPTREE_LIB_PYTHON |
<HOME>/lib/python (portable Python install) |
Only if lib/python/ exists |
SCRIPTREE_APPS |
<HOME>/ScripTreeApps |
Only if ScripTreeApps/ exists |
Recommended use in a tool that ships its own Python helper:
{
"executable": "%SCRIPTREE_LIB_PYTHON%/python.exe",
"argument_template": ["./my_helper.py", "{input}"],
"env": {
"PYTHONPATH": "%SCRIPTREE_LIB_PYPI%"
}
}Move ScripTree to a different folder and the same .scriptree file
keeps working — no path edits required. (See
vendored_dependencies.md for the per-tool
lib/ pattern that complements this.)
When ScripTree's runner spawns ANY tool — Python script, PowerShell,
.bat, native exe — these four guarantees hold for the child's
environment:
SCRIPTREE_HOMEis set to the absolute path of the running ScripTree's install root.SCRIPTREE_LIBis set to<HOME>/lib(whenlib/exists).SCRIPTREE_TOOL_DIRis set to the directory containing the.scriptreefile that was launched.PATHhas<HOME>/lib/combridgeprepended, socombridge.exeresolves by bare name without any discovery code.
This means a tool that needs to find, say, combridge.exe should
not walk upward from its own folder hoping to land inside the
ScripTree install — that fails the moment the tool is installed to
the per-user Personal apps root (%LOCALAPPDATA%\ScripTree\Apps\…).
Instead:
Python
import os
from pathlib import Path
home = os.environ.get("SCRIPTREE_HOME")
if home:
combridge = Path(home) / "lib" / "combridge" / "combridge.exe"PowerShell
$combridge = Join-Path $env:SCRIPTREE_HOME 'lib\combridge\combridge.exe'Batch
"%SCRIPTREE_HOME%\lib\combridge\combridge.exe" word run-script "%1"Anything that respects PATH
combridge word run-script script.csxFor the full authoring contract — including the legacy-fallback
pattern for tools that may be invoked directly outside ScripTree's
runner — see
docs/LLM/scriptree_home_env_var.md.
%VAR% (Windows-style) and $VAR (Unix-style) references are expanded
in:
executableworking_directorypath_prependentries (tool-level, config-level, and global)
Both syntaxes work on every OS — the expansion is done by Python's
os.path.expandvars, which understands both forms regardless of host.
Unknown variables are left as the literal %VAR% text so the failure
is visible at run time instead of silently disappearing.
Argv tokens themselves are NOT auto-expanded — if you need an env var
in a positional argument, set it via tool.env and let the child
process expand it (or hard-code it into the template).
ScripTree lets you set environment variables and PATH prepends on three levels:
- Global — set in Edit → Settings. Applied to every tool run. See settings.md.
- Tool-level — stored in the
.scriptreefile. Applied to every run of the tool, regardless of which configuration is active. - Configuration-level — stored in the sidecar. Layered on top of the tool-level values for the active configuration only.
Default merge order (highest priority last):
os.environ → Global settings → Tool env → Config env
With the "Override" checkbox in Settings, global takes highest priority:
os.environ → Tool env → Config env → Global settings
PATH prepend follows the same pattern — global directories go after tool/config by default, before them when override is enabled.
At run time the child process receives:
os.environ → tool.env → configuration.env (highest priority wins)
PATH prepend entries from the tool and the configuration are both
concatenated (tool first, config second) and prepended to the child's
PATH before spawn.
Open the tool editor and click Edit environment... next to the Environment row in the Tool group box. A popup opens with two text boxes:
Environment variables
One KEY=value per line. Lines starting with # are comments.
MY_VAR=hello
# this is a comment
API_KEY=secret
PATH prepend (directories)
One directory per line.
C:/tools/bin
./vendor
OK parses the text and writes the new env / path_prepend back onto the
ToolDef. Cancel discards the edit. The label next to the button
updates to show a short summary like "2 vars, 1 path" or "no overrides".
The tool-level env is saved as part of the .scriptree file when you
click Save in the main editor.
In the runner, click Env... on the configurations bar. The same dialog
opens, but edits the active configuration's env/path_prepend instead
of the tool's. OK writes the changes back to the active Configuration
and persists the sidecar immediately.
Highest priority wins. Default order (lowest → highest):
os.environ(the ScripTree process's own environment).- Global env (Settings → Global environment) — applies to every tool.
tool.env— overrides the ambient + global.configuration.env— overrides everything above.
Tick Global env overrides tool / config env in Settings to flip the global layer to the top instead:
os.environ < tool.env < configuration.env < global env (overrides)
Earlier in PATH = higher search priority. Default order (highest first):
tool.path_prepend(most specific to this tool).configuration.path_prepend.tree.path_prepend(V3 v0.3.2+) — the parent.scriptreetree's own list, applied to every tool launched through that tree.- Global PATH-prepend (Settings).
- The original
PATHfromos.environ.
So tool-level ./vendor, config-level ./debug-bin, and a tree-level
./shared/bin produce a PATH that starts with:
./vendor ; ./debug-bin ; ./shared/bin ; <global PATH-prepend> ; <original PATH>
Tick Global PATH overrides tool / config PATH in Settings to move global to the front:
<global PATH-prepend> ; ./vendor ; ./debug-bin ; ./shared/bin ; <original PATH>
(Note the asymmetry between env vars and PATH: for env vars, config wins over tool because dict-update applies it last; for PATH search, tool wins over config because it's first in the prepend list. This matches the documented intent on each layer's docstring.)
Directories in the PATH prepend list can be absolute or relative. Relative directories are resolved against:
- The tool's
working_directoryif one is set, else - The executable's parent directory.
So ./vendor in a tool whose working directory is C:/projects/foo
becomes C:/projects/foo/vendor in the child's PATH.
The env editor supports # comment lines and blank lines in both text
boxes. They're preserved when you re-open the editor but stripped when
building the child environment — they exist as a "notes" channel for
you, not for the child process.
When ScripTree tries to launch a tool whose executable can't be found
(e.g. gh.exe got moved or was never installed), the recovery dialog
lets you browse to the file and then choose how to remember it:
- Replace the path stored in this tool's .scriptree — v1 behavior.
The tool's
executablefield is rewritten to the absolute path you picked. Other tools keep their old paths. - Add folder to ScripTree session PATH — modifies
os.environfor the running ScripTree process. Affects every tool you launch this session; lost on exit. - Add folder to this tool's .scriptree path_prepend — appends
the parent directory to the tool's
path_prependlist and saves the .scriptree file. Future launches of this tool (in any future ScripTree session) pick it up. - Add folder to this tree's .scriptreetree path_prepend —
appends to the tree's
path_prepend(new in v0.1.11). Inherited by every tool launched via this tree. - Add folder to user PATH — modifies
HKCU\Environment\Pathvia the registry. Persistent, no admin needed. - Add folder to system PATH — modifies
HKLM\...\Environmentvia the registry. Persistent, requires admin elevation.
Per-file scopes (scriptree / scriptreetree) get an "apply to all
in sidebar" checkbox so a single dialog interaction can fix every
loaded tool/tree at once.
Each scope has its own capability so IT can deny dangerous ones while allowing safer ones. Default deployment ships:
| Capability | Default | Notes |
|---|---|---|
add_to_session_path |
Allowed (file ships) | Lost on exit, no admin |
add_to_scriptree_path_prepend |
Allowed (file ships) | Per-file, low blast radius |
add_to_scriptreetree_path_prepend |
Allowed (file ships) | Per-tree, low blast radius |
add_to_user_path |
Denied (file missing) | Modifies user-wide PATH |
add_to_system_path |
Denied (file missing) | System-wide; requires admin |
To enable user/system PATH at deployment time, an admin creates the
empty file permissions/<category>/add_to_user_path (or
add_to_system_path) in the ScripTree install directory. To deny a
default-allowed scope, mark its permission file read-only.
Denied scopes appear in the dialog as greyed-out radio buttons with a "Disabled by IT — to enable, ask an admin to create..." note, so users always understand why an option isn't available instead of wondering whether it just doesn't exist.
Whenever a non-session scope succeeds, ScripTree also applies the
addition to the current session (via add_to_session_path) so the
in-progress run can pick up the new executable without waiting for
ScripTree to relaunch. This auto-add is gated by the same
add_to_session_path capability — if it's denied, the persistent
change still goes through but the current session won't see it
until the next ScripTree launch.
The recovery dialog's scopes have different effects on the tool
file's executable field — picking a search-path scope is not
the same as just appending to a list:
| Scope | tool.executable after |
tool.path_prepend |
.scriptree saved? |
|---|---|---|---|
| Replace path | new absolute path | unchanged | yes |
| .scriptree path_prepend | basename only | += new directory | yes |
| .scriptreetree path_prepend | basename only | unchanged (the dir lands on the tree, not the tool) | yes |
| User PATH | basename only | unchanged | yes |
| System PATH | basename only | unchanged | yes |
| Session PATH | unchanged (transient) | unchanged | no |
Why basename rewriting? Windows only consults a search path when
the executable is a bare name like gh.exe. If tool.executable
is still an absolute path that no longer exists, no amount of
PATH editing helps — the OS just tries the absolute path verbatim
and fails. Stripping it to the basename forces resolution through
PATH / path_prepend from then on.
For the current run, regardless of scope, ScripTree pins the
already-built argv[0] to the absolute path you picked in the
dialog. That avoids any race where the search-path edit (registry
broadcast, .scriptree save, etc.) hasn't propagated to the
subprocess context yet — the in-progress run launches successfully
and future runs pick the basename up via the search path.
The "Expected location" field in the recovery dialog is editable and drop-aware (v0.1.11). You can:
- Type or paste a path directly, then press Enter or click Apply.
- Drag a file from Explorer onto the field — it replaces the text.
- Click Browse for replacement... for the native file picker.
In scope-picker mode, entering a path that points to a real file auto-reveals the scope picker the same way Browse does. Typing garbage (or erasing the field) hides the picker again until a real path is supplied.
If both tool.env and configuration.env are empty and both
path_prepend lists are empty, ScripTree passes env=None to
subprocess.Popen, which means the child simply inherits the parent
environment unchanged. This is the default state and produces cleaner
error messages when debugging (no giant env block in the process dump).
Suppose you want mytool.py to run with a specific virtualenv's Python
on PATH:
Tool-level env (applies to every run):
VIRTUAL_ENV=C:/projects/mytool/venv
Tool-level PATH prepend:
C:/projects/mytool/venv/Scripts
Now every invocation finds the venv's python.exe first and sets
VIRTUAL_ENV correctly.
Two configurations — dev and prod — with different API keys:
dev config env:
API_URL=https://dev.api.example.com
API_KEY=dev_abcdef
LOG_LEVEL=debug
prod config env:
API_URL=https://api.example.com
API_KEY=prod_xyz
LOG_LEVEL=info
Switch between them with the configurations combo box. The right API key flows into the child process every time.