From 91cb59154dcdad20d1f197cf5c5b16700098ad1d Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Tue, 30 Jun 2026 04:36:01 -0700 Subject: [PATCH] fix(e2e): rm_gate block contract is exit 2, not exit 0 (FPLAN-0289 CI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit beb048d made the Claude bridge propagate a hook's exit code so presence_gate's UserPromptSubmit block can cancel a prompt. Every security gate (rm/git/edit/subagent/presence) already returned exit_code 2 for a block, but the old bridge swallowed it — so test_t2a_rm_gate_blocks pinned exit 0. Update the e2e contract to expect exit 2 + the stdout decision JSON, which is the real, live-proven block signal. Sole CI red on the P1-activation commits: 1 failed, 10116 passed. Fixes both e2e-wheel (all 3 OSes) and Windows Test (full suite includes tests/e2e). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GmDj4eu8aFFVP2rapovqkg --- tests/e2e/test_wiring.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/e2e/test_wiring.py b/tests/e2e/test_wiring.py index b470656a..f0a0e794 100644 --- a/tests/e2e/test_wiring.py +++ b/tests/e2e/test_wiring.py @@ -241,15 +241,20 @@ def hook_workspace(tmp_path_factory: pytest.TempPathFactory) -> Path: def test_t2a_rm_gate_blocks(clean_venv: CleanVenv, hook_workspace: Path) -> None: - """rm -rf is blocked via {"decision":"block"} on STDOUT with exit code 0. + """rm -rf is blocked via {"decision":"block"} on STDOUT with exit code 2. - Corrected contract (NOT exit 2): the bridge writes the engine's block JSON - to stdout and exits 0. + Block contract: every security gate (rm/git/edit/subagent/presence) returns + exit_code 2 plus a block-JSON decision on stdout, and the bridge propagates + that exit code. The non-zero exit is the cross-event block signal — it is + what lets a UserPromptSubmit gate (presence_gate) actually cancel a prompt, + not just PreToolUse. Claude Code surfaces the stdout decision reason either + way. (Pre-FPLAN-0289 the bridge swallowed the exit code, so this test once + pinned exit 0; beb048d corrected the bridge to propagate it.) """ sentinel = f"e2e-block-{uuid.uuid4()}" proc = _fire_hook(clean_venv, hook_workspace, "rm -rf /tmp/x", sentinel) - assert proc.returncode == 0, f"expected exit 0, got {proc.returncode}. stderr:\n{proc.stderr}" + assert proc.returncode == 2, f"expected exit 2 (block), got {proc.returncode}. stderr:\n{proc.stderr}" decision = json.loads(proc.stdout) assert decision.get("decision") == "block", f"stdout was: {proc.stdout!r}"