claude-dev-env 8.54.6 → 8.54.8

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.
@@ -2986,6 +2986,10 @@ test('the help output states that the installer reads only flags', () => {
2986
2986
  assert.equal(helpRun.status, 0, helpRun.stderr);
2987
2987
  assert.match(helpRun.stdout, /reads only flags/i);
2988
2988
  assert.match(helpRun.stdout, /bare path argument carries no meaning/i);
2989
+ assert.match(
2990
+ helpRun.stdout,
2991
+ /--update\s+Full install: copy the package over the prior install, then prune files it no longer ships/,
2992
+ );
2989
2993
  });
2990
2994
 
2991
2995
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.54.6",
3
+ "version": "8.54.8",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,6 +48,7 @@ from dev_env_scripts_constants.account_broker_constants import (
48
48
  ALL_PARENT_CLAUDE_SESSION_VARIABLES,
49
49
  COMMAND_MISSING_EXIT_CODE,
50
50
  REPORT_INDENT_SPACES,
51
+ TIMEOUT_EXIT_CODE,
51
52
  WAIT_EXIT_CODE,
52
53
  utc_time_text as _time_text,
53
54
  )
@@ -287,13 +288,16 @@ def _attempt_once(context: _RunContext, decision: Decision) -> JobOutcome | None
287
288
  account = decision.account
288
289
  try:
289
290
  completion = _invoke(context, account)
290
- except (OSError, subprocess.TimeoutExpired) as error:
291
- status = "timeout" if isinstance(error, subprocess.TimeoutExpired) else "start_failed"
292
- _record_spent_attempt(context, account, status, COMMAND_MISSING_EXIT_CODE)
291
+ except subprocess.TimeoutExpired as error:
292
+ _record_spent_attempt(context, account, "timeout", TIMEOUT_EXIT_CODE)
293
+ context.report.final_decision = decision
294
+ return JobOutcome(TIMEOUT_EXIT_CODE, "", str(error), account.name, tuple(context.all_attempts), "timeout", None, None)
295
+ except OSError as error:
296
+ _record_spent_attempt(context, account, "start_failed", COMMAND_MISSING_EXIT_CODE)
293
297
  context.start_failure_text = str(error)
294
- if isinstance(error, subprocess.TimeoutExpired) or not context.all_readings:
298
+ if not context.all_readings:
295
299
  context.report.final_decision = decision
296
- final_status = "advisor_blocked" if context.product is Product.CLAUDE else status
300
+ final_status = "advisor_blocked" if context.product is Product.CLAUDE else "start_failed"
297
301
  return JobOutcome(COMMAND_MISSING_EXIT_CODE, "", str(error), account.name, tuple(context.all_attempts), final_status, None, None)
298
302
  return None
299
303
  combined = f"{completion.stdout}{completion.stderr}".casefold()
@@ -30,6 +30,8 @@ test('the driver reads an envelope from every stage of a fresh install, and repo
30
30
  assert.match(greenRun.stdout, /\[PASS\] playtest install .* sha256=[0-9a-f]{64}/);
31
31
  assert.match(greenRun.stdout, /\[PASS\] playtest session_start_hook .* sha256=[0-9a-f]{64}/);
32
32
  assert.match(greenRun.stdout, /\[PASS\] playtest blocking_hook .* sha256=[0-9a-f]{64}/);
33
+ const blockingEnvelope = JSON.parse(readFileSync(join(evidenceDirectory, 'blocking_hook.json'), 'utf8'));
34
+ assert.equal(Object.hasOwn(blockingEnvelope, 'permissionDecision'), false);
33
35
  assert.ok(
34
36
  readFileSync(join(scratchHome, SANDBOX_GIT_CONFIG_NAME), 'utf8').startsWith(SANDBOX_GIT_CONFIG_BODY),
35
37
  );
@@ -27,8 +27,6 @@ from dev_env_scripts_constants.claude_account_worker_constants import (
27
27
  PROMPT_FILE_FLAG,
28
28
  REPORT_FILE_FLAG,
29
29
  SINGLE_PROMPT_FLAG,
30
- TIMEOUT_ATTEMPT_STATUS,
31
- TIMEOUT_EXIT_CODE,
32
30
  TIMEOUT_MINUTES_FLAG,
33
31
  UTF8_ENCODING,
34
32
  )
@@ -104,11 +102,10 @@ def run_worker(
104
102
  if outcome.status in {"wait", "exhausted"}:
105
103
  report = wait_report("wait", _wait_reason(outcome), outcome.wait_reset_at)
106
104
  return finalize_report(report_file, report)
107
- is_timed_out = bool(outcome.attempts) and outcome.attempts[-1][1] == TIMEOUT_ATTEMPT_STATUS
108
105
  report = make_report(
109
106
  outcome.account_name or "none",
110
- TIMEOUT_ATTEMPT_STATUS if is_timed_out else outcome.status,
111
- exit_code=TIMEOUT_EXIT_CODE if is_timed_out else outcome.returncode,
107
+ outcome.status,
108
+ exit_code=outcome.returncode,
112
109
  duration_seconds=duration_seconds,
113
110
  stdout_text=outcome.stdout,
114
111
  )
@@ -50,6 +50,7 @@ ALL_CODEX_FLOORS = {
50
50
 
51
51
  WAIT_EXIT_CODE = 3
52
52
  COMMAND_MISSING_EXIT_CODE = 127
53
+ TIMEOUT_EXIT_CODE = 124
53
54
  REPORT_INDENT_SPACES = 2
54
55
  BROKER_STATE_DIRECTORY_NAME = "account-broker"
55
56
  BROKER_STATE_FILE_NAME = "state.json"
@@ -13,10 +13,6 @@ MINIMUM_TIMEOUT_MINUTES: int = 1
13
13
 
14
14
  LAUNCH_FAILURE_EXIT_CODE: int = 1
15
15
 
16
- TIMEOUT_EXIT_CODE: int = 124
17
-
18
- TIMEOUT_ATTEMPT_STATUS: str = "timeout"
19
-
20
16
  CONFIGURATION_FAILURE_EXIT_CODE: int = 2
21
17
 
22
18
  WAIT_DURATION_SECONDS: float = 0.0
@@ -104,8 +100,6 @@ __all__ = [
104
100
  "SINGLE_PROMPT_FLAG",
105
101
  "STDOUT_TAIL_CHARACTER_LIMIT",
106
102
  "SUMMARY_LINE_TEMPLATE",
107
- "TIMEOUT_ATTEMPT_STATUS",
108
- "TIMEOUT_EXIT_CODE",
109
103
  "TIMEOUT_MINUTES_FLAG",
110
104
  "UTF8_ENCODING",
111
105
  "WAIT_DURATION_SECONDS",
@@ -16,9 +16,11 @@ import pytest
16
16
 
17
17
  from dev_env_scripts_constants import account_broker_constants as broker_constants
18
18
  from dev_env_scripts_constants.account_broker_constants import (
19
+ ALL_BATCH_FILE_EXTENSIONS,
19
20
  ALL_CLAUDE_FLOORS,
20
- ALL_PARENT_CLAUDE_SESSION_VARIABLES,
21
21
  ALL_CODEX_FLOORS,
22
+ ALL_PARENT_CLAUDE_SESSION_VARIABLES,
23
+ CMD_SHELL_METACHARACTERS,
22
24
  JobOutcome,
23
25
  )
24
26
  from dev_env_scripts_constants.claude_account_constants import (
@@ -53,6 +55,11 @@ def test_should_keep_codex_floors_tied_to_the_tier_limits() -> None:
53
55
  }
54
56
 
55
57
 
58
+ def test_batch_command_boundaries_include_shell_metacharacters() -> None:
59
+ assert ALL_BATCH_FILE_EXTENSIONS == frozenset({".bat", ".cmd"})
60
+ assert set(CMD_SHELL_METACHARACTERS) == set('&|<>^%!"\r\n')
61
+
62
+
56
63
  def test_should_freeze_job_outcome() -> None:
57
64
  outcome = JobOutcome(0, "", "", None, (), "served", None, None)
58
65
  with pytest.raises(dataclasses.FrozenInstanceError):
@@ -687,6 +687,7 @@ def test_should_stop_after_timeout_without_running_job_again(
687
687
  assert invoked_homes == ["first"]
688
688
  assert outcome.attempts == (("first", "timeout"),)
689
689
  assert outcome.status == "timeout"
690
+ assert outcome.returncode == 124
690
691
  assert outcome.account_name == "first"
691
692
  assert report.final_decision.account.name == "first"
692
693
  assert account_broker.main(("choose", "--product", "codex")) == 0
@@ -696,12 +697,12 @@ def test_should_stop_after_timeout_without_running_job_again(
696
697
  @pytest.mark.parametrize(
697
698
  ("product", "roster_names", "start_error", "expected_code"),
698
699
  (
699
- (Product.CLAUDE, ("first",), subprocess.TimeoutExpired("job", 1), 4),
700
- (Product.CODEX, ("first",), subprocess.TimeoutExpired("job", 1), 127),
700
+ (Product.CLAUDE, ("first",), subprocess.TimeoutExpired("job", 1), 124),
701
+ (Product.CODEX, ("first",), subprocess.TimeoutExpired("job", 1), 124),
701
702
  (Product.CODEX, (), OSError("missing command"), 127),
702
703
  ),
703
704
  )
704
- def test_should_exit_four_only_for_a_blocked_claude_job(
705
+ def test_should_exit_with_job_failure_code(
705
706
  monkeypatch: pytest.MonkeyPatch,
706
707
  tmp_path: Path,
707
708
  product: Product,
@@ -165,7 +165,7 @@ def test_should_preserve_served_failure_code(
165
165
  def test_should_exit_124_with_timeout_reason_when_the_broker_attempt_timed_out(
166
166
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
167
167
  ) -> None:
168
- timed_out = JobOutcome(127, "", "timed out", "extra_2", (("extra_2", "timeout"),), "advisor_blocked", None, None)
168
+ timed_out = JobOutcome(124, "", "timed out", "extra_2", (("extra_2", "timeout"),), "timeout", None, None)
169
169
 
170
170
  exit_code, report, _ = _run_worker(monkeypatch, tmp_path, timed_out)
171
171