diff --git a/cron/scheduler_script.py b/cron/scheduler_script.py index eccf11a5d1..60b2877042 100644 --- a/cron/scheduler_script.py +++ b/cron/scheduler_script.py @@ -343,7 +343,7 @@ def _run_job_script( return False, err try: - from tools.environments.local import build_subprocess_env, strip_launch_profile_env + from tools.environments.local import build_subprocess_env popen_kwargs: dict[str, Any] = {"start_new_session": True} if sys.platform == "win32": popen_kwargs = { @@ -358,8 +358,9 @@ def _run_job_script( # The process env is the LAUNCH profile's. For a job owned by a routed profile, drop that # profile's .env residue from the base first (no-op for the launch profile's own jobs); # the sanitizer then overlays the names the owning profile declares in - # terminal.env_passthrough from its own secret scope (#114209). - env = build_subprocess_env(strip_launch_profile_env(os.environ.copy())) + # terminal.env_passthrough from its own secret scope (#114209). The factory snapshots the + # process env itself — no raw copy at the spawn site (test_subprocess_env_guard). + env = build_subprocess_env(strip_launch_profile=True) env.update(env_overlay) # Subprocess cwd only (default: scripts-dir parent). NEVER os.chdir() the process. # Use the job's workdir as the subprocess cwd when configured, otherwise default to the scripts-dir diff --git a/tools/environments/local.py b/tools/environments/local.py index 76493d77d0..1096911985 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -332,13 +332,19 @@ def _scrub_credentials(env: dict, *, inherit_credentials: bool) -> dict: def build_subprocess_env( base: "Mapping[str, str] | None" = None, *, inherit_profile_home: bool = True, - scrub_secrets: bool = True, extra: "Mapping[str, str] | None" = None) -> dict[str, str]: + scrub_secrets: bool = True, extra: "Mapping[str, str] | None" = None, + strip_launch_profile: bool = False) -> dict[str, str]: """Single factory for child-process envs. ``base=None`` snapshots ``os.environ``. ``scrub_secrets=True`` -> :func:`_sanitize_subprocess_env` (profile home inherent, ``inherit_profile_home`` ignored). ``scrub_secrets=False`` keeps the base byte-for-byte (git credential flows, ``bws``/``op``); ``inherit_profile_home`` - bridges HERMES_HOME + HOME and ``extra`` is applied last so caller overrides win.""" + bridges HERMES_HOME + HOME and ``extra`` is applied last so caller overrides win. + ``strip_launch_profile`` drops the LAUNCH profile's ``.env`` residue from the base first + (:func:`strip_launch_profile_env`; a no-op unless a routed home is active) so a child that + acts for a routed profile sees only that profile's declared names, never the launch profile's.""" env: dict[str, str] = dict(base) if base is not None else os.environ.copy() + if strip_launch_profile: + strip_launch_profile_env(env) if scrub_secrets: return _sanitize_subprocess_env(env, dict(extra) if extra else None) if inherit_profile_home: