From 5af85e3a7a614a1d153468c5f5a78b0a7e8076b4 Mon Sep 17 00:00:00 2001 From: GUOGUO Date: Sun, 25 Oct 2026 20:51:06 +0800 Subject: [PATCH] fix: isolate local command input and clarify audit aggregation Agent-assisted-by: Codex / unknown [effort=unknown] (implementation) Signed-off-by: GUOGUO --- docs/architecture.md | 4 +++ docs/examples/skill-comparison/cases.json | 2 +- src/inferrsi/infra/executors/local.py | 1 + tests/test_local_executor.py | 40 +++++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/test_local_executor.py diff --git a/docs/architecture.md b/docs/architecture.md index 277ab6e5..75917736 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -63,6 +63,10 @@ resources or an existing server. The local executor can run directly on a prepared remote machine; SSH dispatch, distributed leases, automatic planning, patch generation and Hub publication remain future work. +Local executor commands are noninteractive and receive EOF on standard input. +Supply their inputs through command arguments or files rather than the caller's +input stream. + ## Directory map ![Implemented modules mapped to directories](assets/modular-directory-map-en.svg) diff --git a/docs/examples/skill-comparison/cases.json b/docs/examples/skill-comparison/cases.json index 12bc18b1..d9b7f954 100644 --- a/docs/examples/skill-comparison/cases.json +++ b/docs/examples/skill-comparison/cases.json @@ -3,7 +3,7 @@ "id": "audit-threshold", "split": "holdout", "mode": "audit", - "request": "Audit the fixed input report using its contract.", + "request": "Audit the fixed input report using its contract. Compute the median of per-trial request means, including every request in each trial, and the percentage reduction relative to the baseline aggregate.", "contract": {"min_reduction_pct": 20}, "evidence_scope": "http_fixed_workload", "baseline": { diff --git a/src/inferrsi/infra/executors/local.py b/src/inferrsi/infra/executors/local.py index a177f711..049a5fd7 100644 --- a/src/inferrsi/infra/executors/local.py +++ b/src/inferrsi/infra/executors/local.py @@ -48,6 +48,7 @@ def execute_command(command: tuple[str, ...], cwd: Path, trial_dir: Path, deadli argv, cwd=cwd, env=environment, + stdin=subprocess.DEVNULL, stdout=stdout, stderr=stderr, shell=False, diff --git a/tests/test_local_executor.py b/tests/test_local_executor.py new file mode 100644 index 00000000..2e0687cc --- /dev/null +++ b/tests/test_local_executor.py @@ -0,0 +1,40 @@ +import json +import os +import subprocess +import sys +from pathlib import Path + + +def test_local_executor_does_not_inherit_parent_stdin(tmp_path): + child = tmp_path / "child.py" + child.write_text( + "import sys\n" + "from pathlib import Path\n" + "Path('received.txt').write_bytes(sys.stdin.buffer.read())\n", + encoding="utf-8", + ) + parent = tmp_path / "parent.py" + parent.write_text( + "import json\n" + "import sys\n" + "import time\n" + "from pathlib import Path\n" + "from inferrsi.infra.executors.local import execute_command\n" + "command = (sys.executable, str(Path(__file__).with_name('child.py')))\n" + "result = execute_command(command, Path.cwd(), Path('trial'), time.monotonic() + 30)\n" + "print(json.dumps({'status': result['status']}))\n", + encoding="utf-8", + ) + environment = dict(os.environ, PYTHONPATH=str(Path(__file__).resolve().parents[1] / "src")) + completed = subprocess.run( + (sys.executable, str(parent)), + cwd=tmp_path, + env=environment, + input="Unexpected instructions from the caller's input stream.\n", + text=True, + capture_output=True, + check=True, + timeout=60, + ) + assert json.loads(completed.stdout) == {"status": "completed"} + assert (tmp_path / "received.txt").read_bytes() == b""