From 81ce12b6f1093a5bf0e6af2f6ce60a8c51076177 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 13 Oct 2026 00:56:00 +0800 Subject: [PATCH 1/2] fix(launcher): select first Node application for native follow-ups Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- scripts/loopx.ps1 | 2 +- tests/test_scheduler_native_launcher.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/loopx.ps1 b/scripts/loopx.ps1 index e6859bf057..cad683426c 100644 --- a/scripts/loopx.ps1 +++ b/scripts/loopx.ps1 @@ -68,7 +68,7 @@ $isNativeSchedulerFollowup = ( $env:LOOPX_KUNLUNCODE_OUTER_CONTROLLER -ne "1" ) if ($isNativeSchedulerFollowup) { - $node = Get-Command node -CommandType Application -ErrorAction SilentlyContinue + $node = Get-Command node -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $node) { throw "Native scheduler follow-up requires Node.js 22.22.3 or newer" } diff --git a/tests/test_scheduler_native_launcher.py b/tests/test_scheduler_native_launcher.py index b8f2d58664..2bd0be0f96 100644 --- a/tests/test_scheduler_native_launcher.py +++ b/tests/test_scheduler_native_launcher.py @@ -168,9 +168,20 @@ def test_receipt_bound_trae_hint_executes_native_without_python( @pytest.mark.skipif(shutil.which("node") is None, reason="Node.js is required") @pytest.mark.parametrize("launcher", ["unix", "powershell"]) +@pytest.mark.parametrize("node_layout", ["single", "multiple"]) def test_receipt_bound_failure_hint_executes_native_without_python( - tmp_path: Path, launcher: str, + tmp_path: Path, monkeypatch, launcher: str, node_layout: str, ) -> None: + if node_layout == "multiple": + real_node = Path(shutil.which("node")) + later_bin = tmp_path / "later Node candidate" + later_bin.mkdir() + later_node = later_bin / real_node.name + later_node.write_text("This later PATH candidate must not execute.\n", encoding="utf-8") + later_node.chmod(0o755) + monkeypatch.setenv("PATH", os.pathsep.join([ + str(real_node.parent), str(later_bin), os.environ.get("PATH", ""), + ])) registry_path, runtime_root, project = write_cli_fixture( tmp_path / "fixture-failure", scoped_agents=True, From bce869f1b1a9d0ebab278180c6e700225d6945df Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 13 Oct 2026 00:56:00 +0800 Subject: [PATCH 2/2] test(ci): honor Host readback and complete cadence inheritance Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- ...est_machine_configuration_goal_defaults.py | 15 ++++- .../test_scheduler_compat_state_key.py | 64 ++++++++++++++----- tests/test_chat_goal_configuration_api.py | 1 + 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/tests/capabilities/test_machine_configuration_goal_defaults.py b/tests/capabilities/test_machine_configuration_goal_defaults.py index 6dfe7b3fdd..49e9ce0b69 100644 --- a/tests/capabilities/test_machine_configuration_goal_defaults.py +++ b/tests/capabilities/test_machine_configuration_goal_defaults.py @@ -366,10 +366,23 @@ def test_stride_version_inherits_as_one_review_configuration_and_rolls_back(tmp_ configure_goal(registry_path=registry_path, goal_id=GOAL_ID, execution_stride_strategy="off", execute=True) assert _history_goal(registry_path, runtime_root)["execution_profile"]["stride_strategy"] == "off" + clear_options = _goal_capability_options("todo_replan_cadence", None) + before = registry_path.read_bytes() + preview = configure_goal(registry_path=registry_path, goal_id=GOAL_ID, **clear_options) + assert preview["changed"] is True + assert registry_path.read_bytes() == before configure_goal(registry_path=registry_path, goal_id=GOAL_ID, - clear_execution_replan_after_turns=True, execute=True) + execute=True, **clear_options) assert "stride_strategy" not in json.loads(registry_path.read_text())["goals"][0]["execution_profile"] assert _history_goal(registry_path, runtime_root)["execution_profile"]["stride_strategy"] == "evidence_conditioned_v1" + inspected = inspect_goal_capabilities(registry_path=registry_path, runtime_root=runtime_root, + goal_id=GOAL_ID)["configuration"] + review = next(item for item in inspected["capability_catalog"]["capabilities"] + if item["capability_id"] == "todo_replan_cadence") + assert "current" not in review + assert review["effective_configuration"]["source"] == "machine_default" + assert review["effective_configuration"]["inherited"] is True + assert _history_goal(registry_path, runtime_root)["execution_profile"]["replan_after_effective_turns"] == 6 # Unsupported versions fail before any registry mutation. before = registry_path.read_bytes() with pytest.raises(ValueError, match="stride_strategy"): diff --git a/tests/control_plane/test_scheduler_compat_state_key.py b/tests/control_plane/test_scheduler_compat_state_key.py index 2f4f700625..1d271dd644 100644 --- a/tests/control_plane/test_scheduler_compat_state_key.py +++ b/tests/control_plane/test_scheduler_compat_state_key.py @@ -5,12 +5,14 @@ import os from pathlib import Path import subprocess +import sys import pytest from examples.control_plane.quota_plan_fixtures import ( SCOPED_AGENT_ID, write_cli_fixture, + write_codex_heartbeat_fixture, ) from loopx.control_plane.quota.scheduler_ack import ( record_quota_scheduler_ack_for_decision, @@ -43,8 +45,9 @@ def test_compat_followup_preserves_the_read_authority_through_public_entry_point registry, runtime, project = write_cli_fixture( tmp_path / "fixture", scoped_agents=True ) - monkeypatch.setenv("CODEX_HOME", str(tmp_path / "codex-home")) - monkeypatch.delenv("CODEX_THREAD_ID", raising=False) + codex_home = tmp_path / "codex-home" + monkeypatch.setenv("CODEX_HOME", str(codex_home)) + monkeypatch.setenv("CODEX_THREAD_ID", "scheduler-fixture") turn_id = "scheduler-compat-turn" def guard() -> dict: @@ -118,21 +121,50 @@ def guard() -> dict: assert app["stateful_backoff"]["progression_index"] == 1 assert app["stateful_backoff"]["state_key"] == selected_key hint = compat["ack_hint" if operation == "ack" else "failure_hint"] - if caller == "native_cli": - completed = subprocess.run( - [ - str(REPO_ROOT / "scripts" / "loopx"), - "--format", - "json", - *hint["cli_args"], - ], - cwd=project, - check=False, - capture_output=True, - text=True, - env={**os.environ, "LOOPX_PYTHON": str(tmp_path / "python-must-not-run")}, - timeout=30, + if operation == "ack": + write_codex_heartbeat_fixture( + codex_home, goal_id=GOAL_ID, agent_id=SCOPED_AGENT_ID, + rrule="FREQ=MINUTELY;INTERVAL=60", ) + if caller == "native_cli": + def invoke_launcher() -> subprocess.CompletedProcess[str]: + return subprocess.run( + [str(REPO_ROOT / "scripts" / "loopx"), "--format", "json", *hint["cli_args"]], + cwd=project, check=False, capture_output=True, text=True, + env={**os.environ, "LOOPX_PYTHON": ( + sys.executable if operation == "ack" else str(tmp_path / "python-must-not-run") + )}, + timeout=30, + ) + + if operation == "ack": + # Current Host readback belongs to the Python entrypoint; failure + # hints still prove the exact native route with Python unavailable. + manifest = codex_home / "automations/scheduler-fixture/automation.toml" + selected_path = scheduler_state_path( + runtime, goal_id=GOAL_ID, agent_id=SCOPED_AGENT_ID, state_key=selected_key, + ) + selected_bytes = selected_path.read_bytes() + for observed in (None, "FREQ=MINUTELY;INTERVAL=30"): + if observed is None: + manifest.unlink() + else: + write_codex_heartbeat_fixture( + codex_home, goal_id=GOAL_ID, agent_id=SCOPED_AGENT_ID, rrule=observed, + ) + refused = invoke_launcher() + assert refused.returncode == 1, refused.stdout + refused.stderr + refused_result = json.loads(refused.stdout) + assert refused_result["scheduler_commit"]["reason_code"] == "rrule_mismatch" + assert refused_result["scheduler_commit"]["written"] is False + assert selected_path.read_bytes() == selected_bytes + if state_layout == "coexisting": + assert old_path.read_bytes() == old_bytes + write_codex_heartbeat_fixture( + codex_home, goal_id=GOAL_ID, agent_id=SCOPED_AGENT_ID, + rrule="FREQ=MINUTELY;INTERVAL=60", + ) + completed = invoke_launcher() assert completed.returncode == 0, completed.stdout + completed.stderr result = json.loads(completed.stdout) elif caller == "python_cli": diff --git a/tests/test_chat_goal_configuration_api.py b/tests/test_chat_goal_configuration_api.py index cace64e7e9..6172238005 100644 --- a/tests/test_chat_goal_configuration_api.py +++ b/tests/test_chat_goal_configuration_api.py @@ -323,6 +323,7 @@ def test_machine_inheritable_goal_capabilities_can_clear_their_overrides() -> No assert _goal_capability_options("todo_replan_cadence", None) == { "clear_execution_replan_after_todos": True, "clear_execution_replan_after_turns": True, + "clear_execution_stride_strategy": True, } assert _goal_capability_options("change_quality_qualification", None) == { "clear_change_quality_configuration": True