claude-dev-env 8.51.4 → 8.52.1
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.
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +12 -0
- package/_shared/process-tree/scripts/process_tree_kill.py +93 -6
- package/_shared/process-tree/scripts/test_process_tree_kill.py +149 -0
- package/bin/install-pstack-plugin.mjs +68 -33
- package/bin/install-pstack-plugin.test.mjs +77 -32
- package/bin/install.agents-home.test.mjs +1 -0
- package/bin/install.codex-rules.test.mjs +2 -0
- package/bin/install.cursor-rules.test.mjs +1 -0
- package/bin/install.mjs +18 -12
- package/bin/install.prune.test.mjs +1 -0
- package/bin/install.question-presentation.test.mjs +4 -0
- package/bin/install.settings-defaults.test.mjs +1 -0
- package/bin/install.shared-settings.test.mjs +1 -0
- package/bin/install.test.mjs +56 -0
- package/bin/install.transaction.test.mjs +8 -0
- package/bin/install.uninstall-transaction.test.mjs +2 -0
- package/bin/install.update-keeps-hooks.test.mjs +1 -0
- package/package.json +1 -1
- package/scripts/account_broker_support.py +22 -6
- package/scripts/test_account_broker_support.py +83 -24
|
@@ -5,6 +5,8 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
import shutil
|
|
7
7
|
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
8
10
|
from datetime import datetime, timezone
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
from types import SimpleNamespace
|
|
@@ -222,16 +224,85 @@ def test_should_raise_windows_lock_error_other_than_contention(tmp_path: Path) -
|
|
|
222
224
|
assert all_calls == [lock_descriptor]
|
|
223
225
|
|
|
224
226
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
GRANDCHILD_HEARTBEAT_SCRIPT = (
|
|
228
|
+
"import pathlib, sys, time\n"
|
|
229
|
+
"heartbeat = pathlib.Path(sys.argv[1])\n"
|
|
230
|
+
"for each_beat in range(300):\n"
|
|
231
|
+
" heartbeat.write_text(str(each_beat), encoding='utf-8')\n"
|
|
232
|
+
" time.sleep(0.1)\n"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
CHILD_SCRIPT = (
|
|
236
|
+
"import subprocess, sys, time\n"
|
|
237
|
+
"subprocess.Popen([sys.executable, '-c', sys.argv[1], sys.argv[2]])\n"
|
|
238
|
+
"time.sleep(60)\n"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_should_end_grandchildren_when_the_job_times_out(tmp_path: Path) -> None:
|
|
243
|
+
heartbeat_file = tmp_path / "heartbeat.txt"
|
|
244
|
+
|
|
245
|
+
with pytest.raises(subprocess.TimeoutExpired):
|
|
246
|
+
support._run_captured_subprocess(
|
|
247
|
+
[sys.executable, "-c", CHILD_SCRIPT, GRANDCHILD_HEARTBEAT_SCRIPT, str(heartbeat_file)],
|
|
248
|
+
timeout=5,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
beat_after_timeout = heartbeat_file.read_text(encoding="utf-8")
|
|
252
|
+
time.sleep(0.5)
|
|
253
|
+
assert heartbeat_file.read_text(encoding="utf-8") == beat_after_timeout
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_should_end_grandchildren_when_the_broker_is_interrupted(
|
|
257
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
258
|
+
) -> None:
|
|
259
|
+
heartbeat_file = tmp_path / "heartbeat.txt"
|
|
260
|
+
original_communicate = subprocess.Popen.communicate
|
|
261
|
+
|
|
262
|
+
def interrupted_communicate(process: subprocess.Popen[bytes], *args: object, **kwargs: object) -> object:
|
|
263
|
+
try:
|
|
264
|
+
return original_communicate(process, *args, timeout=5)
|
|
265
|
+
except subprocess.TimeoutExpired:
|
|
266
|
+
raise KeyboardInterrupt from None
|
|
267
|
+
|
|
268
|
+
monkeypatch.setattr(subprocess.Popen, "communicate", interrupted_communicate)
|
|
269
|
+
|
|
270
|
+
with pytest.raises(KeyboardInterrupt):
|
|
271
|
+
support._run_captured_subprocess(
|
|
272
|
+
[sys.executable, "-c", CHILD_SCRIPT, GRANDCHILD_HEARTBEAT_SCRIPT, str(heartbeat_file)],
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
beat_after_interrupt = heartbeat_file.read_text(encoding="utf-8")
|
|
276
|
+
time.sleep(0.5)
|
|
277
|
+
assert heartbeat_file.read_text(encoding="utf-8") == beat_after_interrupt
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _record_launches(monkeypatch: pytest.MonkeyPatch) -> list[list[str]]:
|
|
228
281
|
all_launched_argv: list[list[str]] = []
|
|
229
282
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
283
|
+
class FakeProcess:
|
|
284
|
+
returncode = 0
|
|
285
|
+
|
|
286
|
+
def __init__(self, all_argv: list[str], **options: object) -> None:
|
|
287
|
+
all_launched_argv.append(list(all_argv))
|
|
288
|
+
|
|
289
|
+
def __enter__(self) -> "FakeProcess":
|
|
290
|
+
return self
|
|
291
|
+
|
|
292
|
+
def __exit__(self, *exception_info: object) -> None:
|
|
293
|
+
return None
|
|
233
294
|
|
|
234
|
-
|
|
295
|
+
def communicate(self, **options: object) -> tuple[None, None]:
|
|
296
|
+
return None, None
|
|
297
|
+
|
|
298
|
+
monkeypatch.setattr(support.subprocess, "Popen", FakeProcess)
|
|
299
|
+
return all_launched_argv
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_should_launch_the_path_resolved_command_file(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
303
|
+
resolved_command = r"C:\Users\someone\AppData\Roaming\npm\claude.cmd"
|
|
304
|
+
monkeypatch.setattr(shutil, "which", lambda name: resolved_command if name == "claude" else None)
|
|
305
|
+
all_launched_argv = _record_launches(monkeypatch)
|
|
235
306
|
|
|
236
307
|
completion = support.subprocess_runner(["claude", "-p"], input=b"", encoding="utf-8", errors="replace")
|
|
237
308
|
|
|
@@ -241,7 +312,7 @@ def test_should_launch_the_path_resolved_command_file(monkeypatch: pytest.Monkey
|
|
|
241
312
|
|
|
242
313
|
def test_should_refuse_to_launch_a_bare_command_missing_from_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
243
314
|
monkeypatch.setattr(shutil, "which", lambda name: None)
|
|
244
|
-
monkeypatch.setattr(support.subprocess, "
|
|
315
|
+
monkeypatch.setattr(support.subprocess, "Popen", lambda *arguments, **options: pytest.fail("a missing command ran"))
|
|
245
316
|
|
|
246
317
|
with pytest.raises(FileNotFoundError) as raised:
|
|
247
318
|
support.subprocess_runner(["claude", "-p"])
|
|
@@ -256,7 +327,7 @@ def test_should_refuse_a_batch_file_argument_that_cmd_would_parse(
|
|
|
256
327
|
) -> None:
|
|
257
328
|
resolved_command = r"C:\Users\someone\AppData\Roaming\npm\claude.cmd"
|
|
258
329
|
monkeypatch.setattr(shutil, "which", lambda name: resolved_command)
|
|
259
|
-
monkeypatch.setattr(support.subprocess, "
|
|
330
|
+
monkeypatch.setattr(support.subprocess, "Popen", lambda *arguments, **options: pytest.fail("a batch file ran"))
|
|
260
331
|
|
|
261
332
|
with pytest.raises(OSError) as raised:
|
|
262
333
|
support.subprocess_runner(["claude", "-p", f"fix{metacharacter}test"])
|
|
@@ -269,7 +340,7 @@ def test_should_refuse_a_batch_file_argument_that_cmd_would_parse(
|
|
|
269
340
|
def test_should_refuse_metacharacters_for_an_uppercase_bat_extension(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
270
341
|
resolved_command = r"C:\tools\CLAUDE.BAT"
|
|
271
342
|
monkeypatch.setattr(shutil, "which", lambda name: resolved_command)
|
|
272
|
-
monkeypatch.setattr(support.subprocess, "
|
|
343
|
+
monkeypatch.setattr(support.subprocess, "Popen", lambda *arguments, **options: pytest.fail("a batch file ran"))
|
|
273
344
|
|
|
274
345
|
with pytest.raises(OSError) as raised:
|
|
275
346
|
support.subprocess_runner(["claude", "a & b"])
|
|
@@ -280,13 +351,7 @@ def test_should_refuse_metacharacters_for_an_uppercase_bat_extension(monkeypatch
|
|
|
280
351
|
def test_should_launch_a_batch_file_with_flags_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
281
352
|
resolved_command = r"C:\Users\someone\AppData\Roaming\npm\claude.cmd"
|
|
282
353
|
monkeypatch.setattr(shutil, "which", lambda name: resolved_command)
|
|
283
|
-
all_launched_argv
|
|
284
|
-
|
|
285
|
-
def fake_run(all_argv: list[str], **options: object) -> subprocess.CompletedProcess[bytes]:
|
|
286
|
-
all_launched_argv.append(list(all_argv))
|
|
287
|
-
return subprocess.CompletedProcess(all_argv, 0)
|
|
288
|
-
|
|
289
|
-
monkeypatch.setattr(support.subprocess, "run", fake_run)
|
|
354
|
+
all_launched_argv = _record_launches(monkeypatch)
|
|
290
355
|
|
|
291
356
|
support.subprocess_runner(["claude", "-p", "--output-format", "json", "--model", "opus"], input=b"a & b")
|
|
292
357
|
|
|
@@ -296,13 +361,7 @@ def test_should_launch_a_batch_file_with_flags_only(monkeypatch: pytest.MonkeyPa
|
|
|
296
361
|
def test_should_pass_metacharacters_to_an_executable_unchanged(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
297
362
|
resolved_command = "/usr/local/bin/claude"
|
|
298
363
|
monkeypatch.setattr(shutil, "which", lambda name: resolved_command)
|
|
299
|
-
all_launched_argv
|
|
300
|
-
|
|
301
|
-
def fake_run(all_argv: list[str], **options: object) -> subprocess.CompletedProcess[bytes]:
|
|
302
|
-
all_launched_argv.append(list(all_argv))
|
|
303
|
-
return subprocess.CompletedProcess(all_argv, 0)
|
|
304
|
-
|
|
305
|
-
monkeypatch.setattr(support.subprocess, "run", fake_run)
|
|
364
|
+
all_launched_argv = _record_launches(monkeypatch)
|
|
306
365
|
|
|
307
366
|
support.subprocess_runner(["claude", "-p", 'say "a & b" | 100%!'])
|
|
308
367
|
|