From 331086c7fc87781193e7c2b34f1a22a9181fb8ea Mon Sep 17 00:00:00 2001 From: Mohammed Alkindi Date: Thu, 8 Oct 2026 22:53:26 -0700 Subject: [PATCH 1/2] test(hooks): align cursor smoke expectations with native flat handlers #3149 renders Cursor hooks under camelCase native events with flat handlers, but two integration tests still expected the old shape and have kept the scheduled smoke red since: - the root source drift test read the ownership sidecar under PreToolUse instead of preToolUse, and seeded a nested user hook that Cursor's strict validator now rejects on reinstall; - the audit contract test expected a nested group in .cursor/hooks.json to be silently not-applicable, while audit now reports it as incomplete coverage because Cursor does not load nested handlers. Refs #3166 --- .../test_audit_primitive_contract.py | 11 ++++- .../test_hook_root_source_drift_e2e.py | 44 ++++++++++++------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/tests/integration/test_audit_primitive_contract.py b/tests/integration/test_audit_primitive_contract.py index 36ecd624ce..c767caf400 100644 --- a/tests/integration/test_audit_primitive_contract.py +++ b/tests/integration/test_audit_primitive_contract.py @@ -705,9 +705,16 @@ def test_preserved_nested_command_layout_is_visible_non_prompt( payload = json.dumps({"version": 1, "hooks": {"PreToolUse": [entry]}}) path.write_text(payload, encoding="utf-8") result = scan_project_result(project, targets=(profile,)) - assert result.incomplete == () assert result.findings_by_file == {} - assert [entry.status for entry in result.inventory] == ["not-applicable"] + if target == "cursor" and nested: + # Cursor loads only flat handlers, so a nested group is reported, not read. + assert [(entry.pointer, entry.diagnostic) for entry in result.incomplete] == [ + ("/hooks/PreToolUse/0", "native hook event requires flat handlers") + ] + assert [entry.status for entry in result.inventory] == ["incomplete"] + else: + assert result.incomplete == () + assert [entry.status for entry in result.inventory] == ["not-applicable"] assert path.read_text() == payload assert not list(project.glob("HOOK_MUST_NOT_RUN*")) diff --git a/tests/integration/test_hook_root_source_drift_e2e.py b/tests/integration/test_hook_root_source_drift_e2e.py index 5da9354d3d..eb3dc99db8 100644 --- a/tests/integration/test_hook_root_source_drift_e2e.py +++ b/tests/integration/test_hook_root_source_drift_e2e.py @@ -49,7 +49,7 @@ def _run(apm_binary_path: Path, cwd: Path, *args: str) -> subprocess.CompletedPr "cursor", ".cursor/hooks.json", ".cursor/apm-hooks.json", - "PreToolUse", + "preToolUse", id="cursor", ), pytest.param( @@ -68,6 +68,30 @@ def _run(apm_binary_path: Path, cwd: Path, *args: str) -> subprocess.CompletedPr ), ] +# Cursor's native schema accepts only flat handlers, so a nested user entry +# would make the second install reject the config instead of preserving it. +_FLAT_HANDLER_TARGETS = frozenset({"cursor"}) + + +def _user_owned_entry(target: str) -> dict: + command = {"type": "command", "command": "echo user-owned"} + if target in _FLAT_HANDLER_TARGETS: + return command + return {"matcher": "Bash", "hooks": [command]} + + +def _is_user_owned(entry: object) -> bool: + if not isinstance(entry, dict): + return False + handlers = entry.get("hooks") + if isinstance(handlers, list): + return ( + bool(handlers) + and isinstance(handlers[0], dict) + and (handlers[0].get("command") == "echo user-owned") + ) + return entry.get("command") == "echo user-owned" + def _load_sources( project: Path, @@ -178,12 +202,8 @@ def test_root_hook_source_drift_heals_on_reinstall( # Append a user-owned entry directly to the settings file (never in the sidecar). settings_path = project / settings_rel settings_data = json.loads(settings_path.read_text(encoding="utf-8")) - settings_data.setdefault("hooks", {}).setdefault(event_key, []).append( - { - "matcher": "Bash", - "hooks": [{"type": "command", "command": "echo user-owned"}], - } - ) + user_entry = _user_owned_entry(target) + settings_data.setdefault("hooks", {}).setdefault(event_key, []).append(user_entry) settings_path.write_text(json.dumps(settings_data), encoding="utf-8") # Second install: must heal the stale marker without touching the user-owned entry. @@ -197,15 +217,7 @@ def test_root_hook_source_drift_heals_on_reinstall( settings_data = json.loads(settings_path.read_text(encoding="utf-8")) entries = settings_data.get("hooks", {}).get(event_key, []) - user_owned = [ - e - for e in entries - if isinstance(e, dict) - and isinstance(e.get("hooks"), list) - and e["hooks"] - and isinstance(e["hooks"][0], dict) - and e["hooks"][0].get("command") == "echo user-owned" - ] + user_owned = [e for e in entries if _is_user_owned(e)] assert len(user_owned) == 1, ( f"User-owned hook entry must survive healing for {target}; entries={entries}" ) From 2d60268a18de838e6da2649a3d932516ba08b102 Mon Sep 17 00:00:00 2001 From: Mohammed Alkindi Date: Fri, 9 Oct 2026 20:42:51 -0700 Subject: [PATCH 2/2] test(hooks): use native cursor event and exact user-entry match --- .../integration/test_audit_primitive_contract.py | 5 +++-- .../test_hook_root_source_drift_e2e.py | 15 +-------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/tests/integration/test_audit_primitive_contract.py b/tests/integration/test_audit_primitive_contract.py index c767caf400..262872609b 100644 --- a/tests/integration/test_audit_primitive_contract.py +++ b/tests/integration/test_audit_primitive_contract.py @@ -702,14 +702,15 @@ def test_preserved_nested_command_layout_is_visible_non_prompt( marker = project / "HOOK_MUST_NOT_RUN" command = {"type": "command", "command": f"touch {marker}{_BIDI}"} entry = {"matcher": "*", "hooks": [command]} if nested else command - payload = json.dumps({"version": 1, "hooks": {"PreToolUse": [entry]}}) + event = "preToolUse" if target == "cursor" else "PreToolUse" + payload = json.dumps({"version": 1, "hooks": {event: [entry]}}) path.write_text(payload, encoding="utf-8") result = scan_project_result(project, targets=(profile,)) assert result.findings_by_file == {} if target == "cursor" and nested: # Cursor loads only flat handlers, so a nested group is reported, not read. assert [(entry.pointer, entry.diagnostic) for entry in result.incomplete] == [ - ("/hooks/PreToolUse/0", "native hook event requires flat handlers") + ("/hooks/preToolUse/0", "native hook event requires flat handlers") ] assert [entry.status for entry in result.inventory] == ["incomplete"] else: diff --git a/tests/integration/test_hook_root_source_drift_e2e.py b/tests/integration/test_hook_root_source_drift_e2e.py index eb3dc99db8..baa63f72f1 100644 --- a/tests/integration/test_hook_root_source_drift_e2e.py +++ b/tests/integration/test_hook_root_source_drift_e2e.py @@ -80,19 +80,6 @@ def _user_owned_entry(target: str) -> dict: return {"matcher": "Bash", "hooks": [command]} -def _is_user_owned(entry: object) -> bool: - if not isinstance(entry, dict): - return False - handlers = entry.get("hooks") - if isinstance(handlers, list): - return ( - bool(handlers) - and isinstance(handlers[0], dict) - and (handlers[0].get("command") == "echo user-owned") - ) - return entry.get("command") == "echo user-owned" - - def _load_sources( project: Path, settings_rel: str, @@ -217,7 +204,7 @@ def test_root_hook_source_drift_heals_on_reinstall( settings_data = json.loads(settings_path.read_text(encoding="utf-8")) entries = settings_data.get("hooks", {}).get(event_key, []) - user_owned = [e for e in entries if _is_user_owned(e)] + user_owned = [e for e in entries if e == user_entry] assert len(user_owned) == 1, ( f"User-owned hook entry must survive healing for {target}; entries={entries}" )