claude-dev-env 8.54.5 → 8.54.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,7 +11,8 @@ The hook stays silent when:
|
|
|
11
11
|
- the stop is already a retry after a block (stop_hook_active is true);
|
|
12
12
|
- the session shows no title tool: no title tool named anywhere in the
|
|
13
13
|
transcript, and either no remote session id in the environment or a
|
|
14
|
-
deferred tool listing that leaves the title tool out
|
|
14
|
+
deferred tool listing that leaves the title tool out, or an earlier
|
|
15
|
+
Stop block the session never met;
|
|
15
16
|
- the transcript cannot be read.
|
|
16
17
|
"""
|
|
17
18
|
|
|
@@ -32,6 +33,7 @@ from hooks_constants.session_title_constants import (
|
|
|
32
33
|
ALLOW_EXIT_CODE,
|
|
33
34
|
BLOCK_DECISION,
|
|
34
35
|
DEFERRED_TOOL_ATTACHMENT_TYPES,
|
|
36
|
+
HOOK_BLOCKING_ERROR_ATTACHMENT_TYPE,
|
|
35
37
|
REMOTE_SESSION_ENVIRONMENT_VARIABLE,
|
|
36
38
|
REMOTE_TITLE_TOOL_NAME,
|
|
37
39
|
STOP_BLOCK_REASON,
|
|
@@ -85,6 +87,15 @@ def _lists_deferred_tools(all_entry_fields: dict[str, object]) -> bool:
|
|
|
85
87
|
return isinstance(attachment, dict) and attachment.get("type") in DEFERRED_TOOL_ATTACHMENT_TYPES
|
|
86
88
|
|
|
87
89
|
|
|
90
|
+
def _is_stop_block(all_entry_fields: dict[str, object]) -> bool:
|
|
91
|
+
attachment = all_entry_fields.get("attachment")
|
|
92
|
+
return (
|
|
93
|
+
isinstance(attachment, dict)
|
|
94
|
+
and attachment.get("type") == HOOK_BLOCKING_ERROR_ATTACHMENT_TYPE
|
|
95
|
+
and attachment.get("hookEvent") == STOP_GATE_EVENT_NAME
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
88
99
|
def _is_title_call(entry_type: object, all_block_fields: dict[str, object]) -> bool:
|
|
89
100
|
return (
|
|
90
101
|
entry_type == "assistant"
|
|
@@ -132,6 +143,7 @@ def stop_block_reason(all_entries: list[dict[str, object]], is_remote_session: b
|
|
|
132
143
|
prompt -> work, no title call => block reason
|
|
133
144
|
no title tool anywhere, not remote => None
|
|
134
145
|
remote, tool list lacks the title tool => None
|
|
146
|
+
remote, earlier Stop block never met => None
|
|
135
147
|
|
|
136
148
|
Args:
|
|
137
149
|
all_entries: The transcript entries, oldest first.
|
|
@@ -144,7 +156,8 @@ def stop_block_reason(all_entries: list[dict[str, object]], is_remote_session: b
|
|
|
144
156
|
each_name for each_entry in all_entries for each_name in _title_tool_names_in(each_entry)
|
|
145
157
|
]
|
|
146
158
|
if not all_seen_tool_names and (
|
|
147
|
-
not is_remote_session
|
|
159
|
+
not is_remote_session
|
|
160
|
+
or any(_lists_deferred_tools(each) or _is_stop_block(each) for each in all_entries)
|
|
148
161
|
):
|
|
149
162
|
return None
|
|
150
163
|
if _title_set_since(all_entries[_turn_start_index(all_entries) :]):
|
|
@@ -199,6 +199,43 @@ def test_should_block_a_remote_session_whose_tool_list_names_the_title_tool(
|
|
|
199
199
|
assert json.loads(stdout_text)["decision"] == "block"
|
|
200
200
|
|
|
201
201
|
|
|
202
|
+
def earlier_stop_block() -> dict[str, object]:
|
|
203
|
+
return {
|
|
204
|
+
"type": "attachment",
|
|
205
|
+
"attachment": {"type": "hook_blocking_error", "hookEvent": "Stop"},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_should_stay_silent_in_a_remote_session_after_an_unmet_earlier_block(
|
|
210
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
|
|
211
|
+
) -> None:
|
|
212
|
+
monkeypatch.setenv(REMOTE_SESSION_VARIABLE, "cse_1")
|
|
213
|
+
all_entries = [
|
|
214
|
+
prompt("Fix the runner"),
|
|
215
|
+
earlier_stop_block(),
|
|
216
|
+
prompt("Next task"),
|
|
217
|
+
tool_call("t1", "Bash"),
|
|
218
|
+
tool_result("t1"),
|
|
219
|
+
]
|
|
220
|
+
assert run_gate(monkeypatch, capsys, tmp_path, all_entries) == (0, "")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_should_block_after_an_earlier_block_once_the_title_tool_is_listed(
|
|
224
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
|
|
225
|
+
) -> None:
|
|
226
|
+
monkeypatch.setenv(REMOTE_SESSION_VARIABLE, "cse_1")
|
|
227
|
+
all_entries = [
|
|
228
|
+
deferred_tools(REMOTE_TITLE_TOOL),
|
|
229
|
+
prompt("Fix the runner"),
|
|
230
|
+
earlier_stop_block(),
|
|
231
|
+
prompt("Next task"),
|
|
232
|
+
tool_call("t1", "Bash"),
|
|
233
|
+
tool_result("t1"),
|
|
234
|
+
]
|
|
235
|
+
_, stdout_text = run_gate(monkeypatch, capsys, tmp_path, all_entries)
|
|
236
|
+
assert json.loads(stdout_text)["decision"] == "block"
|
|
237
|
+
|
|
238
|
+
|
|
202
239
|
def test_should_stay_silent_on_the_retry_after_a_block(
|
|
203
240
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
|
|
204
241
|
) -> None:
|
|
@@ -15,6 +15,7 @@ ALLOW_EXIT_CODE = 0
|
|
|
15
15
|
BLOCK_EXIT_CODE = 2
|
|
16
16
|
BLOCK_DECISION = "block"
|
|
17
17
|
DEFERRED_TOOL_ATTACHMENT_TYPES = frozenset({"deferred_tools_delta", "deferred_tools_record"})
|
|
18
|
+
HOOK_BLOCKING_ERROR_ATTACHMENT_TYPE = "hook_blocking_error"
|
|
18
19
|
|
|
19
20
|
STATUS_PREFIX_PATTERN = re.compile("^(\U0001f6a9|\u2705|\u23f3)\ufe0f? (?=\\S)")
|
|
20
21
|
MAXIMUM_NAME_LENGTH = 25
|