From 8c9758d0430c8ee0ef3ee3a08a0e57a89ae6947f Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Tue, 3 Mar 2026 12:16:32 +0900 Subject: [PATCH 1/3] Fix #4321: Set CurrentUICulture to en-US in PowerFx eval() On non-English systems, CultureInfo.CurrentUICulture causes PowerFx to emit localized error messages. The existing ValueError guard only matches English strings ("isn't recognized", "Name isn't valid"), so undefined variable errors crash instead of returning None gracefully. Fix: save and restore CurrentUICulture alongside CurrentCulture before calling engine.eval(), ensuring error messages are always in English. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../_workflows/_declarative_base.py | 3 +++ .../tests/test_powerfx_yaml_compatibility.py | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py index 687dad096b..2b147ef738 100644 --- a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py @@ -388,11 +388,14 @@ def eval(self, expression: str) -> Any: from System.Globalization import CultureInfo original_culture = CultureInfo.CurrentCulture + original_ui_culture = CultureInfo.CurrentUICulture CultureInfo.CurrentCulture = CultureInfo("en-US") + CultureInfo.CurrentUICulture = CultureInfo("en-US") try: return engine.eval(formula, symbols=symbols) finally: CultureInfo.CurrentCulture = original_culture + CultureInfo.CurrentUICulture = original_ui_culture except ValueError as e: error_msg = str(e) # Handle undefined variable errors gracefully by returning None diff --git a/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py b/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py index 9591dc05cb..cd29d87ee9 100644 --- a/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py +++ b/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py @@ -493,6 +493,29 @@ async def test_undefined_nested_variable_returns_none(self, mock_state): result = state.eval("=Local.Something.Nested.Deep") assert result is None + async def test_undefined_variable_returns_none_with_non_english_ui_culture(self, mock_state): + """Test that undefined variables return None even when CurrentUICulture is non-English. + + Regression test for #4321: on non-English systems, CurrentUICulture causes + PowerFx to emit localized error messages that don't match the English + string guards ("isn't recognized", "Name isn't valid"), crashing the workflow. + The fix sets CurrentUICulture to en-US alongside CurrentCulture before eval. + """ + from System.Globalization import CultureInfo + + state = DeclarativeWorkflowState(mock_state) + state.initialize() + + # Simulate a non-English UI culture (e.g. Italian) + original_ui_culture = CultureInfo.CurrentUICulture + CultureInfo.CurrentUICulture = CultureInfo("it-IT") + try: + # Should return None, not raise ValueError with Italian error text + result = state.eval("=Local.StatusConversationId") + assert result is None + finally: + CultureInfo.CurrentUICulture = original_ui_culture + class TestStringInterpolation: """Test string interpolation patterns.""" From 59177193cf3985b420ba22aba534c7a4946d2433 Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Tue, 3 Mar 2026 12:21:43 +0900 Subject: [PATCH 2/3] Reuse single CultureInfo instance to avoid redundant allocations Cache CultureInfo("en-US") in a local variable instead of instantiating it twice per eval() call, as suggested in PR review. Fixes #4321 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../_workflows/_declarative_base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py index 2b147ef738..01a68e6a8e 100644 --- a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py @@ -389,8 +389,9 @@ def eval(self, expression: str) -> Any: original_culture = CultureInfo.CurrentCulture original_ui_culture = CultureInfo.CurrentUICulture - CultureInfo.CurrentCulture = CultureInfo("en-US") - CultureInfo.CurrentUICulture = CultureInfo("en-US") + en_us_culture = CultureInfo("en-US") + CultureInfo.CurrentCulture = en_us_culture + CultureInfo.CurrentUICulture = en_us_culture try: return engine.eval(formula, symbols=symbols) finally: From ed0913e78ff9729f335a97e58af17c7e2c5334ab Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Tue, 3 Mar 2026 12:57:04 +0900 Subject: [PATCH 3/3] Add assertion for CurrentUICulture restoration after eval Assert that the production code's finally-block correctly restores CurrentUICulture to it-IT after eval returns, covering future regressions where the culture could leak. The CultureInfo caching suggestion (comment #2) was already implemented in the production code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../declarative/tests/test_powerfx_yaml_compatibility.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py b/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py index cd29d87ee9..8ea3c3af57 100644 --- a/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py +++ b/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py @@ -513,6 +513,8 @@ async def test_undefined_variable_returns_none_with_non_english_ui_culture(self, # Should return None, not raise ValueError with Italian error text result = state.eval("=Local.StatusConversationId") assert result is None + # Verify the production code restored CurrentUICulture after eval + assert str(CultureInfo.CurrentUICulture) == str(CultureInfo("it-IT")) finally: CultureInfo.CurrentUICulture = original_ui_culture