claude-dev-env 8.46.26 → 8.46.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.46.26",
3
+ "version": "8.46.27",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import errno
6
6
  import json
7
7
  import os
8
+ import shutil
8
9
  import subprocess
9
10
  import sys
10
11
  import tempfile
@@ -428,13 +429,21 @@ def _resume_id(all_argv: Sequence[str]) -> str | None:
428
429
  return None
429
430
 
430
431
 
432
+ def _resolve_command(all_argv: Sequence[str]) -> list[str]:
433
+ command_name, *all_arguments = all_argv
434
+ resolved_command = shutil.which(command_name)
435
+ if resolved_command is None and not os.path.dirname(command_name):
436
+ raise FileNotFoundError(errno.ENOENT, "command not found on PATH", command_name)
437
+ return [resolved_command or command_name, *all_arguments]
438
+
439
+
431
440
  def _run_captured_subprocess(all_argv: Sequence[str], **options: object) -> subprocess.CompletedProcess[str]:
432
441
  encoding = str(options.get("encoding") or "utf-8")
433
442
  errors = str(options.get("errors") or "replace")
434
443
  stdin_bytes = options.get("input")
435
444
  with tempfile.TemporaryFile() as stdout_file, tempfile.TemporaryFile() as stderr_file:
436
445
  completion = subprocess.run(
437
- list(all_argv),
446
+ _resolve_command(all_argv),
438
447
  input=stdin_bytes,
439
448
  stdout=stdout_file,
440
449
  stderr=stderr_file,
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import io
5
+ import shutil
5
6
  import subprocess
6
7
  from datetime import datetime, timedelta, timezone
7
8
  from pathlib import Path
@@ -696,6 +697,20 @@ def test_should_run_job_through_override(
696
697
  assert calls == [b"input"]
697
698
 
698
699
 
700
+ def test_should_record_start_failure_without_launching_a_command_missing_from_path(monkeypatch: pytest.MonkeyPatch) -> None:
701
+ monkeypatch.setitem(account_broker.all_product_adapters, Product.CLAUDE, _adapter(
702
+ (_account("extra", Product.CLAUDE),), {"extra": _meters(80, 80)}
703
+ ))
704
+ monkeypatch.setattr(shutil, "which", lambda name: None)
705
+ monkeypatch.setattr(account_broker.support.subprocess, "run", lambda *arguments, **options: pytest.fail("a missing command ran"))
706
+
707
+ outcome, _ = account_broker._execute(Product.CLAUDE, ("claude", "-p"), now=NOW)
708
+
709
+ assert outcome.attempts == (("extra", "start_failed"),)
710
+ assert outcome.status == "exhausted"
711
+ assert outcome.returncode == WAIT_EXIT_CODE
712
+
713
+
699
714
  @pytest.mark.parametrize("marked_name", ["retired", "first"])
700
715
  def test_should_run_past_a_stored_spent_mark_beyond_the_platform_range(
701
716
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], marked_name: str
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import errno
4
4
  import json
5
5
  import os
6
+ import shutil
6
7
  import subprocess
7
8
  from datetime import datetime, timezone
8
9
  from pathlib import Path
@@ -161,3 +162,31 @@ def test_should_raise_windows_lock_error_other_than_contention(tmp_path: Path) -
161
162
  os.close(lock_descriptor)
162
163
 
163
164
  assert all_calls == [lock_descriptor]
165
+
166
+
167
+ def test_should_launch_the_path_resolved_command_file(monkeypatch: pytest.MonkeyPatch) -> None:
168
+ resolved_command = r"C:\Users\someone\AppData\Roaming\npm\claude.cmd"
169
+ monkeypatch.setattr(shutil, "which", lambda name: resolved_command if name == "claude" else None)
170
+ all_launched_argv: list[list[str]] = []
171
+
172
+ def fake_run(all_argv: list[str], **options: object) -> subprocess.CompletedProcess[bytes]:
173
+ all_launched_argv.append(list(all_argv))
174
+ return subprocess.CompletedProcess(all_argv, 0)
175
+
176
+ monkeypatch.setattr(support.subprocess, "run", fake_run)
177
+
178
+ completion = support.subprocess_runner(["claude", "-p"], input=b"", encoding="utf-8", errors="replace")
179
+
180
+ assert all_launched_argv == [[resolved_command, "-p"]]
181
+ assert completion.returncode == 0
182
+
183
+
184
+ def test_should_refuse_to_launch_a_bare_command_missing_from_path(monkeypatch: pytest.MonkeyPatch) -> None:
185
+ monkeypatch.setattr(shutil, "which", lambda name: None)
186
+ monkeypatch.setattr(support.subprocess, "run", lambda *arguments, **options: pytest.fail("a missing command ran"))
187
+
188
+ with pytest.raises(FileNotFoundError) as raised:
189
+ support.subprocess_runner(["claude", "-p"])
190
+
191
+ assert raised.value.errno == errno.ENOENT
192
+ assert raised.value.filename == "claude"