Skip to content

Commit 1ed7618

Browse files
authored
fix(runner): make RunnerContext.get_env() read live from os.environ (#885)
## Summary This is a followup PR to ensure that #726 data is pushed to Langfuse for product instrumentation - **Fix stale environment snapshot in `RunnerContext`**: `get_env()` was reading from a frozen dict captured once at construction. Runtime mutations to `os.environ` (by `POST /workflow`, `POST /repos/add`, auth refresh) were invisible to any code using `context.get_env()`. - **Root cause**: `__post_init__` merged `os.environ` into `self.environment` and `get_env()` read only from that dict. The context is never recreated — `mark_dirty()` resets the bridge but reuses the same stale context. - **Fix**: Store explicit constructor overrides in `_overrides`. `get_env()` now checks overrides first, then falls through to live `os.environ`. `self.environment` remains populated for backward compatibility. ## Changes | File | Change | |------|--------| | `ambient_runner/platform/context.py` | Store `_overrides` in `__post_init__`; `get_env()` reads live from `os.environ` with overrides winning | | `tests/test_context.py` | Regression tests: runtime mutation visibility + override precedence | ## Test plan - [x] New `test_get_env_sees_os_environ_mutations_after_creation` — creates context, mutates `os.environ` after, asserts `get_env()` returns mutated value - [x] New `test_explicit_overrides_win_over_os_environ` — constructor overrides take precedence even after `os.environ` mutation - [x] All 73 existing tests pass without modification (test_context, test_bridge_claude, test_observability) - [ ] CI green Made with [Cursor](https://cursor.com) Co-authored-by: bgregor <348865+bobbravo2@users.noreply.github.com>
1 parent f4c5cb5 commit 1ed7618

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

components/runners/ambient-runner/ambient_runner/platform/context.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@ class RunnerContext:
2828
metadata: Dict[str, Any] = field(default_factory=dict)
2929

3030
def __post_init__(self) -> None:
31-
"""Merge environment variables (explicit overrides win)."""
31+
"""Store explicit overrides for precedence in get_env(); keep environment populated for backward compatibility."""
32+
self._overrides = dict(self.environment)
3233
self.environment = {**os.environ, **self.environment}
3334

3435
def get_env(self, key: str, default: Optional[str] = None) -> Optional[str]:
35-
"""Get an environment variable value."""
36-
return self.environment.get(key, default)
36+
"""Get an environment variable, with explicit overrides winning. Reads live from os.environ for non-overridden keys."""
37+
overrides = getattr(self, "_overrides", None)
38+
if overrides is None:
39+
return self.environment.get(key, default)
40+
if key in overrides:
41+
return overrides[key]
42+
return os.environ.get(key, default)
3743

3844
def set_metadata(self, key: str, value: Any) -> None:
3945
"""Set a metadata value."""
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Tests for RunnerContext."""
2+
3+
import os
4+
5+
import pytest
6+
7+
from ambient_runner.platform.context import RunnerContext
8+
9+
10+
class TestRunnerContextGetEnv:
11+
"""Verify get_env() reads live from os.environ and respects explicit overrides."""
12+
13+
def test_get_env_sees_os_environ_mutations_after_creation(self):
14+
"""get_env() must return current os.environ value when key is mutated at runtime."""
15+
key = "_RUNNER_CONTEXT_TEST_MUTATION_"
16+
try:
17+
if key in os.environ:
18+
del os.environ[key]
19+
ctx = RunnerContext(session_id="s1", workspace_path="/tmp")
20+
assert ctx.get_env(key) is None
21+
os.environ[key] = "runtime-mutated"
22+
assert ctx.get_env(key) == "runtime-mutated"
23+
finally:
24+
os.environ.pop(key, None)
25+
26+
def test_explicit_overrides_win_over_os_environ(self):
27+
"""Explicit overrides passed at construction must take precedence over os.environ."""
28+
key = "_RUNNER_CONTEXT_TEST_OVERRIDE_"
29+
try:
30+
os.environ[key] = "from-os-environ"
31+
ctx = RunnerContext(
32+
session_id="s1",
33+
workspace_path="/tmp",
34+
environment={key: "from-constructor"},
35+
)
36+
assert ctx.get_env(key) == "from-constructor"
37+
os.environ[key] = "mutated-after-creation"
38+
assert ctx.get_env(key) == "from-constructor"
39+
finally:
40+
os.environ.pop(key, None)

0 commit comments

Comments
 (0)