Summary
Six tests in tests/test_kunluncode_goal_mode.py deterministically fail on a machine where Node.js is present and new enough, all with EffectRuntimeStartupError: LoopX Effect runtime requires Node.js 22.6.0 or newer (node_unavailable):
test_mcp_uses_kunluncode_profile_and_rejects_agent_impersonation
test_fastmcp_complete_task_forwards_complete_task_lease_fence
test_mcp_spends_only_after_typed_completed_state
test_complete_task_spends_bound_to_selected_todo_and_refreshes_state
test_complete_task_classifies_terminal_no_selection_and_fails_closed
test_complete_task_fails_closed_on_unparseable_snapshot
Root cause
These tests mock the subprocess layer with:
monkeypatch.setattr(goal_mode_mcp.subprocess, "run", capture)
goal_mode_mcp.subprocess and loopx.control_plane.effect_runtime.subprocess are the same stdlib module object, so this assignment patches subprocess.run globally for the whole test process. complete_task then goes through settle_host_todo_completion -> effect_runtime_request -> _start_runtime -> _node_executable -> _probe_node, whose subprocess.run([node, "--version"]) now returns the fake CompletedProcess(command, 0, "not-json", ""). The version regex does not match "not-json", the probe returns probe_failed, and every effect-runtime path raises node_unavailable.
Reproduction (in-process):
import loopx.goal_mode_mcp as gm, loopx.control_plane.effect_runtime as er
print(gm.subprocess is er.subprocess) # True -- one shared module object
er.subprocess.run = lambda command, **kw: __import__("subprocess").CompletedProcess(command, 0, "not-json", "")
print(er._probe_node()) # ('probe_failed', '/opt/homebrew/bin/node', None)
Observed with node v26.8.1 on PATH: _probe_node() returns ('ready', ...) when called directly, but the same probe inside these tests is poisoned. A/B verified: the same six tests fail identically with and without any other local changes, i.e. this is pre-existing (checked on the #4088 head 012fd40).
Why CI may not have caught it: these tests pass whenever the effect runtime is already up (or the probe result is otherwise bypassed) before the mock is installed -- execution-order dependent.
Suggested fix
Patch a module-local runner instead of the shared subprocess module attribute, e.g. give GoalModeMCPControlPlane an injectable runner (some call sites already have control.command_prefix style seams) and monkeypatch that, so the stdlib module object is never mutated.
Interim mitigation
The six tests can carry @pytest.mark.skip referencing this issue until the runner seam lands.
Summary
Six tests in
tests/test_kunluncode_goal_mode.pydeterministically fail on a machine where Node.js is present and new enough, all withEffectRuntimeStartupError: LoopX Effect runtime requires Node.js 22.6.0 or newer (node_unavailable):test_mcp_uses_kunluncode_profile_and_rejects_agent_impersonationtest_fastmcp_complete_task_forwards_complete_task_lease_fencetest_mcp_spends_only_after_typed_completed_statetest_complete_task_spends_bound_to_selected_todo_and_refreshes_statetest_complete_task_classifies_terminal_no_selection_and_fails_closedtest_complete_task_fails_closed_on_unparseable_snapshotRoot cause
These tests mock the subprocess layer with:
goal_mode_mcp.subprocessandloopx.control_plane.effect_runtime.subprocessare the same stdlib module object, so this assignment patchessubprocess.runglobally for the whole test process.complete_taskthen goes throughsettle_host_todo_completion -> effect_runtime_request -> _start_runtime -> _node_executable -> _probe_node, whosesubprocess.run([node, "--version"])now returns the fakeCompletedProcess(command, 0, "not-json", ""). The version regex does not match"not-json", the probe returnsprobe_failed, and every effect-runtime path raisesnode_unavailable.Reproduction (in-process):
Observed with node v26.8.1 on PATH:
_probe_node()returns('ready', ...)when called directly, but the same probe inside these tests is poisoned. A/B verified: the same six tests fail identically with and without any other local changes, i.e. this is pre-existing (checked on the #4088 head012fd40).Why CI may not have caught it: these tests pass whenever the effect runtime is already up (or the probe result is otherwise bypassed) before the mock is installed -- execution-order dependent.
Suggested fix
Patch a module-local runner instead of the shared subprocess module attribute, e.g. give
GoalModeMCPControlPlanean injectablerunner(some call sites already havecontrol.command_prefixstyle seams) and monkeypatch that, so the stdlib module object is never mutated.Interim mitigation
The six tests can carry
@pytest.mark.skipreferencing this issue until the runner seam lands.