Skip to content

test: 6 kunluncode tests leak their subprocess mock into effect_runtime._probe_node and fail as node_unavailable #4151

Description

@Job28703

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions