claude-dev-env 8.46.21 → 8.46.23
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 +1 -1
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +1 -1
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +1 -1
- package/scripts/resolve_worker_spawn.py +1 -1
- package/scripts/test_claude_account_worker_process.py +47 -0
- package/scripts/test_claude_account_worker_report.py +157 -0
- package/scripts/test_invoke_code_review_cli.py +2 -1
- package/scripts/test_resolve_worker_spawn.py +6 -5
package/package.json
CHANGED
|
@@ -10,5 +10,5 @@ ALL_USAGE_LIMIT_SIGNATURES: tuple[str, ...] = (
|
|
|
10
10
|
)
|
|
11
11
|
"""Case-insensitive substrings that mark a non-zero exit as a usage-limit refusal."""
|
|
12
12
|
|
|
13
|
-
CHAIN_CONFIG_ERROR_EXIT_CODE: int =
|
|
13
|
+
CHAIN_CONFIG_ERROR_EXIT_CODE: int = 5
|
|
14
14
|
"""CLI exit code when the chain configuration is missing or invalid."""
|
|
@@ -493,7 +493,7 @@ SPAWN_SERVED_EXIT_CODE: int = 0
|
|
|
493
493
|
SPAWN_EXHAUSTED_EXIT_CODE: int = 2
|
|
494
494
|
"""CLI exit code when no dispatcher tier served the call."""
|
|
495
495
|
|
|
496
|
-
SPAWN_CONFIG_ERROR_EXIT_CODE: int =
|
|
496
|
+
SPAWN_CONFIG_ERROR_EXIT_CODE: int = 5
|
|
497
497
|
"""CLI exit code when the claude chain configuration is missing or invalid."""
|
|
498
498
|
|
|
499
499
|
RESULT_KEY_TIER_USED: str = "tier_used"
|
|
@@ -607,7 +607,7 @@ def main(all_command_arguments: list[str]) -> int:
|
|
|
607
607
|
|
|
608
608
|
A timeout outside the accepted bounds is a config fault: the run prints a
|
|
609
609
|
structured outcome whose attempt reason is ``timeout_out_of_bounds`` and
|
|
610
|
-
exits ``
|
|
610
|
+
exits ``5``, rather than raising out of the CLI. A broker configuration error,
|
|
611
611
|
or an unknown host profile refused by ``detect_host_profile``, prints the
|
|
612
612
|
same exit code with the refusal text as its output.
|
|
613
613
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from claude_account_worker_process import invoke_worker
|
|
6
|
+
from dev_env_scripts_constants.account_broker_constants import JobOutcome, Product
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_should_send_job_to_runner_and_return_outcome_with_elapsed_seconds(
|
|
10
|
+
tmp_path: Path,
|
|
11
|
+
) -> None:
|
|
12
|
+
expected_outcome = JobOutcome(
|
|
13
|
+
0, "{}", "", "extra_2", (("extra_2", "served"),), "served", None, None
|
|
14
|
+
)
|
|
15
|
+
captured_calls: list[tuple[Product, list[str], dict[str, object]]] = []
|
|
16
|
+
clock_readings = iter([100.0, 107.5])
|
|
17
|
+
|
|
18
|
+
def recording_runner(
|
|
19
|
+
product: Product, argv: list[str], **options: object
|
|
20
|
+
) -> JobOutcome:
|
|
21
|
+
captured_calls.append((product, argv, options))
|
|
22
|
+
return expected_outcome
|
|
23
|
+
|
|
24
|
+
outcome, elapsed_seconds = invoke_worker(
|
|
25
|
+
all_arguments=["claude", "-p"],
|
|
26
|
+
cwd=tmp_path,
|
|
27
|
+
prompt_text="standalone brief",
|
|
28
|
+
timeout_minutes=2,
|
|
29
|
+
runner=recording_runner,
|
|
30
|
+
monotonic_clock=lambda: next(clock_readings),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert outcome is expected_outcome
|
|
34
|
+
assert elapsed_seconds == 7.5
|
|
35
|
+
assert captured_calls == [
|
|
36
|
+
(
|
|
37
|
+
Product.CLAUDE,
|
|
38
|
+
["claude", "-p"],
|
|
39
|
+
{
|
|
40
|
+
"timeout_seconds": 120,
|
|
41
|
+
"stdin_text": "standalone brief",
|
|
42
|
+
"cwd": tmp_path,
|
|
43
|
+
"encoding": "utf-8",
|
|
44
|
+
"errors": "replace",
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from claude_account_worker_report import (
|
|
10
|
+
WorkerReport,
|
|
11
|
+
finalize_report,
|
|
12
|
+
make_report,
|
|
13
|
+
pre_launch_failure_report,
|
|
14
|
+
wait_report,
|
|
15
|
+
write_report,
|
|
16
|
+
)
|
|
17
|
+
from dev_env_scripts_constants.account_broker_constants import WAIT_EXIT_CODE
|
|
18
|
+
from dev_env_scripts_constants.claude_account_worker_constants import (
|
|
19
|
+
STDOUT_TAIL_CHARACTER_LIMIT,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_should_build_wait_report_with_wait_exit_code_and_reset_time() -> None:
|
|
24
|
+
reset_at = datetime(2026, 10, 4, 12, tzinfo=timezone.utc)
|
|
25
|
+
|
|
26
|
+
report = wait_report("wait", "no account has room", reset_at)
|
|
27
|
+
|
|
28
|
+
assert report == WorkerReport(
|
|
29
|
+
account="wait",
|
|
30
|
+
reason="no account has room",
|
|
31
|
+
exit_code=WAIT_EXIT_CODE,
|
|
32
|
+
duration_seconds=0.0,
|
|
33
|
+
payload=None,
|
|
34
|
+
is_error=False,
|
|
35
|
+
wait_reset_at=reset_at,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_should_build_report_from_json_result_and_round_duration() -> None:
|
|
40
|
+
report = make_report(
|
|
41
|
+
"extra_2",
|
|
42
|
+
"served",
|
|
43
|
+
exit_code=0,
|
|
44
|
+
duration_seconds=1.23456,
|
|
45
|
+
stdout_text='{"result":"ready","is_error":false}',
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert report.payload == "ready"
|
|
49
|
+
assert report.is_error is False
|
|
50
|
+
assert report.duration_seconds == 1.235
|
|
51
|
+
assert report.wait_reset_at is None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_should_mark_report_as_error_when_json_flags_error() -> None:
|
|
55
|
+
report = make_report(
|
|
56
|
+
"extra_2",
|
|
57
|
+
"served",
|
|
58
|
+
exit_code=0,
|
|
59
|
+
duration_seconds=0.0,
|
|
60
|
+
stdout_text='{"result":"failed","is_error":true}',
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
assert report.payload == "failed"
|
|
64
|
+
assert report.is_error is True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_should_mark_report_as_error_when_exit_code_is_nonzero() -> None:
|
|
68
|
+
report = make_report(
|
|
69
|
+
"extra_2",
|
|
70
|
+
"served",
|
|
71
|
+
exit_code=5,
|
|
72
|
+
duration_seconds=0.0,
|
|
73
|
+
stdout_text='{"result":"ready","is_error":false}',
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
assert report.exit_code == 5
|
|
77
|
+
assert report.is_error is True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_should_keep_bounded_stdout_tail_when_stdout_is_not_json() -> None:
|
|
81
|
+
stdout_text = "x" * STDOUT_TAIL_CHARACTER_LIMIT + "tail"
|
|
82
|
+
|
|
83
|
+
report = make_report(
|
|
84
|
+
"extra_2", "served", exit_code=0, duration_seconds=0.0, stdout_text=stdout_text
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
assert report.payload == stdout_text[-STDOUT_TAIL_CHARACTER_LIMIT:]
|
|
88
|
+
assert len(report.payload) == STDOUT_TAIL_CHARACTER_LIMIT
|
|
89
|
+
assert report.is_error is True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_should_drop_payload_when_json_has_no_result_key() -> None:
|
|
93
|
+
report = make_report(
|
|
94
|
+
"extra_2",
|
|
95
|
+
"served",
|
|
96
|
+
exit_code=0,
|
|
97
|
+
duration_seconds=0.0,
|
|
98
|
+
stdout_text='{"other":1}',
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert report.payload is None
|
|
102
|
+
assert report.is_error is True
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_should_build_pre_launch_failure_report_with_zero_duration() -> None:
|
|
106
|
+
report = pre_launch_failure_report("none", "prompt file is unreadable", 1)
|
|
107
|
+
|
|
108
|
+
assert report.account == "none"
|
|
109
|
+
assert report.reason == "prompt file is unreadable"
|
|
110
|
+
assert report.exit_code == 1
|
|
111
|
+
assert report.duration_seconds == 0.0
|
|
112
|
+
assert report.is_error is True
|
|
113
|
+
assert report.wait_reset_at is None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_should_write_report_as_json_and_create_parent_directory(
|
|
117
|
+
tmp_path: Path,
|
|
118
|
+
) -> None:
|
|
119
|
+
reset_at = datetime(2026, 10, 4, 12, tzinfo=timezone.utc)
|
|
120
|
+
report_file = tmp_path / "nested" / "report.json"
|
|
121
|
+
|
|
122
|
+
write_report(report_file, wait_report("wait", "no account has room", reset_at))
|
|
123
|
+
|
|
124
|
+
report_text = report_file.read_text(encoding="utf-8")
|
|
125
|
+
assert report_text.endswith("\n")
|
|
126
|
+
assert json.loads(report_text) == {
|
|
127
|
+
"account": "wait",
|
|
128
|
+
"reason": "no account has room",
|
|
129
|
+
"exit_code": WAIT_EXIT_CODE,
|
|
130
|
+
"duration_seconds": 0.0,
|
|
131
|
+
"result": None,
|
|
132
|
+
"is_error": False,
|
|
133
|
+
"wait_reset_at": reset_at.isoformat(),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_should_finalize_report_by_writing_it_printing_summary_and_returning_exit_code(
|
|
138
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
139
|
+
) -> None:
|
|
140
|
+
report_file = tmp_path / "report.json"
|
|
141
|
+
report = make_report(
|
|
142
|
+
"extra_2",
|
|
143
|
+
"served",
|
|
144
|
+
exit_code=4,
|
|
145
|
+
duration_seconds=2.0,
|
|
146
|
+
stdout_text='{"result":"done","is_error":false}',
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
exit_code = finalize_report(report_file, report)
|
|
150
|
+
|
|
151
|
+
assert exit_code == 4
|
|
152
|
+
written_report = json.loads(report_file.read_text(encoding="utf-8"))
|
|
153
|
+
assert written_report["result"] == "done"
|
|
154
|
+
assert written_report["wait_reset_at"] is None
|
|
155
|
+
assert (
|
|
156
|
+
capsys.readouterr().out == f"account=extra_2 exit_code=4 report={report_file}\n"
|
|
157
|
+
)
|
|
@@ -19,7 +19,7 @@ from _code_review_test_support import (
|
|
|
19
19
|
install_seams,
|
|
20
20
|
run_review_cli,
|
|
21
21
|
)
|
|
22
|
-
from dev_env_scripts_constants.account_broker_constants import BrokerConfigurationError
|
|
22
|
+
from dev_env_scripts_constants.account_broker_constants import BrokerConfigurationError, WAIT_EXIT_CODE
|
|
23
23
|
from dev_env_scripts_constants.claude_chain_constants import (
|
|
24
24
|
CHAIN_CONFIG_ERROR_EXIT_CODE,
|
|
25
25
|
)
|
|
@@ -82,6 +82,7 @@ def test_cli_emits_json_on_chain_configuration_error(
|
|
|
82
82
|
exit_code = run_review_cli(working_directory, session_model=FIXTURE_SESSION_OPUS)
|
|
83
83
|
|
|
84
84
|
assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
85
|
+
assert exit_code != WAIT_EXIT_CODE
|
|
85
86
|
captured = capsys.readouterr()
|
|
86
87
|
parsed_payload = json.loads(captured.out)
|
|
87
88
|
assert parsed_payload == {
|
|
@@ -299,12 +299,12 @@ def test_grok_usage_limited_on_claude_host_requires_agent(
|
|
|
299
299
|
def test_out_of_bounds_timeout_is_reported_as_config_not_worker_failure(
|
|
300
300
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
301
301
|
) -> None:
|
|
302
|
-
"""A refused timeout prints a structured outcome and exits
|
|
302
|
+
"""A refused timeout prints a structured outcome and exits 5, never a traceback.
|
|
303
303
|
|
|
304
304
|
::
|
|
305
305
|
|
|
306
|
-
--timeout-seconds 0 ok: reason timeout_out_of_bounds, exit
|
|
307
|
-
--timeout-seconds 5401 ok: reason timeout_out_of_bounds, exit
|
|
306
|
+
--timeout-seconds 0 ok: reason timeout_out_of_bounds, exit 5
|
|
307
|
+
--timeout-seconds 5401 ok: reason timeout_out_of_bounds, exit 5
|
|
308
308
|
"""
|
|
309
309
|
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
310
310
|
_install_seams(monkeypatch, grok_outcome=_grok_ok())
|
|
@@ -350,7 +350,7 @@ def test_out_of_bounds_timeout_is_refused_when_the_grok_tier_is_unreachable(
|
|
|
350
350
|
::
|
|
351
351
|
|
|
352
352
|
preflight unusable, --timeout-seconds 5401
|
|
353
|
-
ok: reason timeout_out_of_bounds, exit
|
|
353
|
+
ok: reason timeout_out_of_bounds, exit 5, no tier invoked
|
|
354
354
|
"""
|
|
355
355
|
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
356
356
|
call_log = _install_seams(
|
|
@@ -496,7 +496,7 @@ def test_tier_three_wait_reports_reset_and_stops(
|
|
|
496
496
|
assert call_log.claude_calls == 1
|
|
497
497
|
|
|
498
498
|
|
|
499
|
-
def
|
|
499
|
+
def test_config_error_exit_code_differs_from_the_wait_exit_code(
|
|
500
500
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
501
501
|
) -> None:
|
|
502
502
|
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
@@ -523,6 +523,7 @@ def test_config_error_returns_exit_three(
|
|
|
523
523
|
)
|
|
524
524
|
|
|
525
525
|
assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE
|
|
526
|
+
assert exit_code != WAIT_EXIT_CODE
|
|
526
527
|
captured = capsys.readouterr()
|
|
527
528
|
parsed_payload = json.loads(captured.out)
|
|
528
529
|
assert parsed_payload[RESULT_KEY_OK] is False
|