claude-dev-env 2.5.0 → 2.7.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.
Files changed (153) hide show
  1. package/CLAUDE.md +20 -57
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -1
  3. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
  4. package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
  5. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
  6. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
  7. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
  8. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
  9. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
  12. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
  13. package/_shared/pr-loop/worker-spawn.md +3 -1
  14. package/agents/CLAUDE.md +1 -1
  15. package/agents/code-verifier.md +36 -7
  16. package/bin/codex-compat.mjs +104 -0
  17. package/bin/codex-compat.test.mjs +51 -0
  18. package/codex-capability-map.json +13 -0
  19. package/docs/CODE_RULES.md +2 -0
  20. package/docs/codex-compatibility.md +25 -0
  21. package/docs/nas-ssh-invocation.md +96 -12
  22. package/docs/references/code-review-enforcement.md +31 -6
  23. package/hooks/blocking/CLAUDE.md +3 -0
  24. package/hooks/blocking/config/code_review_enforcement_constants.py +40 -10
  25. package/hooks/blocking/config/test_code_review_enforcement_constants.py +56 -3
  26. package/hooks/blocking/eli11_reply_enforcer.py +479 -0
  27. package/hooks/blocking/gh_body_arg_blocker.py +1 -1
  28. package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
  29. package/hooks/blocking/shell_substitution_blocker.py +129 -0
  30. package/hooks/blocking/state_description_blocker.py +1 -1
  31. package/hooks/blocking/stop_dispatcher.py +1 -1
  32. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
  33. package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
  34. package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
  35. package/hooks/blocking/test_stop_dispatcher.py +23 -0
  36. package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +4 -8
  38. package/hooks/blocking/unscoped_search_blocker.py +391 -0
  39. package/hooks/git-hooks/CLAUDE.md +3 -0
  40. package/hooks/git-hooks/conftest.py +30 -0
  41. package/hooks/git-hooks/gate_utils.py +2 -2
  42. package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
  43. package/hooks/git-hooks/pre_push.py +75 -4
  44. package/hooks/git-hooks/pre_push_base_reference.py +166 -0
  45. package/hooks/git-hooks/test_config.py +0 -15
  46. package/hooks/git-hooks/test_gate_utils.py +3 -15
  47. package/hooks/git-hooks/test_pre_commit.py +1 -15
  48. package/hooks/git-hooks/test_pre_push.py +236 -27
  49. package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
  50. package/hooks/hooks.json +0 -12
  51. package/hooks/hooks_constants/CLAUDE.md +5 -1
  52. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
  53. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
  54. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
  55. package/hooks/hooks_constants/shell_command_segments.py +82 -0
  56. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
  57. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
  58. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
  59. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
  60. package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
  61. package/package.json +4 -2
  62. package/rules/CLAUDE.md +17 -23
  63. package/rules/agent-spawn-protocol.md +6 -6
  64. package/rules/anti-corollary-tests.md +1 -1
  65. package/rules/bdd.md +1 -1
  66. package/rules/cleanup-temp-files.md +10 -4
  67. package/rules/code-standards.md +7 -0
  68. package/rules/conservative-action.md +1 -5
  69. package/rules/context7.md +0 -4
  70. package/rules/destructive-commands.md +47 -0
  71. package/rules/doc-inventory-integrity.md +48 -0
  72. package/rules/doc-prose-cuts.md +58 -0
  73. package/rules/docstring-prose-matches-implementation.md +10 -2
  74. package/rules/durable-post-artifacts.md +0 -4
  75. package/rules/eli11-replies.md +31 -0
  76. package/rules/explore-thoroughly.md +4 -4
  77. package/rules/falsify-before-green.md +68 -0
  78. package/rules/file-global-constants.md +1 -1
  79. package/rules/filesystem-search.md +51 -0
  80. package/rules/gh-cli-conventions.md +27 -0
  81. package/rules/git-workflow.md +26 -0
  82. package/rules/hedging-claims.md +9 -0
  83. package/rules/long-horizon-autonomy.md +0 -4
  84. package/rules/measurement-denominators.md +48 -0
  85. package/rules/nas-ssh-invocation.md +23 -5
  86. package/rules/parallel-tools.md +2 -2
  87. package/rules/plain-illustrative-docstrings.md +3 -7
  88. package/rules/plain-language.md +2 -0
  89. package/rules/proof-of-work-pr-comments.md +0 -4
  90. package/rules/re-stage-before-commit.md +2 -0
  91. package/rules/research-mode.md +10 -0
  92. package/rules/shell-invocation.md +21 -0
  93. package/rules/testing.md +4 -0
  94. package/rules/verified-commit-gate-skip.md +3 -27
  95. package/rules/verify-before-asking.md +5 -0
  96. package/rules/windows-filesystem-safe.md +1 -1
  97. package/rules/workers-done-before-complete.md +4 -0
  98. package/scripts/CLAUDE.md +3 -3
  99. package/scripts/Migrate-ShellPolicy.ps1 +1 -1
  100. package/scripts/codex_capability_bridge.py +171 -0
  101. package/scripts/codex_compat_materializer.py +1087 -0
  102. package/scripts/codex_compat_watcher.py +502 -0
  103. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  104. package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
  105. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +79 -13
  106. package/scripts/grok_headless_runner.py +213 -16
  107. package/scripts/invoke_code_review.py +11 -4
  108. package/scripts/resolve_worker_spawn.py +56 -10
  109. package/scripts/spawn_grok_batch.py +43 -22
  110. package/scripts/sync_to_cursor/rules.py +0 -10
  111. package/scripts/test_grok_headless_runner.py +592 -10
  112. package/scripts/test_invoke_code_review.py +143 -0
  113. package/scripts/test_invoke_code_review_chain.py +1 -1
  114. package/scripts/test_invoke_code_review_contract.py +1 -1
  115. package/scripts/test_resolve_worker_spawn.py +179 -15
  116. package/scripts/test_spawn_grok_batch.py +225 -22
  117. package/scripts/tests/test_code_review_constants.py +80 -0
  118. package/scripts/tests/test_codex_capability_bridge.py +91 -0
  119. package/scripts/tests/test_codex_compat_materializer.py +632 -0
  120. package/scripts/tests/test_codex_compat_watcher.py +599 -0
  121. package/scripts/tests/test_sync_to_cursor.py +0 -1
  122. package/skills/autoconverge/workflow/converge.contract.test.mjs +28 -6
  123. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +73 -0
  124. package/skills/autoconverge/workflow/converge.mjs +64 -15
  125. package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
  126. package/skills/condensing-instructions/SKILL.md +42 -51
  127. package/skills/fresh-branch/CLAUDE.md +1 -1
  128. package/skills/fresh-branch/SKILL.md +5 -6
  129. package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
  130. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
  131. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
  132. package/skills/grok-spawn/SKILL.md +5 -3
  133. package/skills/grok-spawn/reference/flag-profiles.md +3 -1
  134. package/skills/orchestrator/SKILL.md +23 -9
  135. package/skills/orchestrator-refresh/SKILL.md +20 -1
  136. package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  137. package/skills/session-log/SKILL.md +1 -1
  138. package/rules/claude-md-orphan-file.md +0 -28
  139. package/rules/cleanup-command-forms.md +0 -23
  140. package/rules/code-reviews.md +0 -11
  141. package/rules/env-var-table-code-drift.md +0 -10
  142. package/rules/gh-body-file.md +0 -5
  143. package/rules/gh-paginate.md +0 -3
  144. package/rules/hook-prose-matches-detector.md +0 -15
  145. package/rules/no-historical-clutter.md +0 -26
  146. package/rules/no-inline-destructive-literals.md +0 -9
  147. package/rules/no-justification-noise.md +0 -61
  148. package/rules/package-inventory-stale-entry.md +0 -25
  149. package/rules/right-sized-engineering.md +0 -28
  150. package/rules/self-contained-docs.md +0 -17
  151. package/rules/shell-invocation-policy.md +0 -5
  152. package/rules/state-what-is.md +0 -25
  153. package/rules/tdd.md +0 -7
@@ -0,0 +1,143 @@
1
+ """Specifications for what a blocked mint tells the person it blocked.
2
+
3
+ Enforcement refuses every push until a clean stamp covers the branch surface,
4
+ and ``invoke_code_review.py --record-stamp`` is the only way to mint one. When
5
+ that call cannot run, the reason it names is the person's only route back to a
6
+ working push.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ import invoke_code_review as invoker
16
+ from claude_chain_runner import ChainConfigurationError, ChainInvocationOutcome
17
+ from _code_review_test_support import FIXTURE_SESSION_OPUS
18
+ from dev_env_scripts_constants.code_review_constants import (
19
+ DEFAULT_CODE_REVIEW_EFFORT,
20
+ PERMISSION_MODE_ACCEPT_EDITS,
21
+ PERMISSION_MODE_BYPASS,
22
+ REVIEW_PERMISSION_MODE,
23
+ )
24
+
25
+
26
+ def test_review_arguments_carry_the_permission_mode_this_caller_resolves() -> None:
27
+ """The review command asks for a permission mode the binary accepts here.
28
+
29
+ The binary refuses the bypass mode outright for a root caller, so asking
30
+ for it there means no review runs and no stamp is ever minted.
31
+ """
32
+ all_arguments = invoker.build_code_review_arguments(DEFAULT_CODE_REVIEW_EFFORT)
33
+
34
+ assert REVIEW_PERMISSION_MODE in all_arguments
35
+
36
+
37
+ def test_the_resolved_permission_mode_is_one_the_binary_knows() -> None:
38
+ assert REVIEW_PERMISSION_MODE in (
39
+ PERMISSION_MODE_ACCEPT_EDITS,
40
+ PERMISSION_MODE_BYPASS,
41
+ )
42
+
43
+
44
+ CHAIN_CONFIG_REMEDY_TEXT: str = (
45
+ "Claude chain config not found at the path this specification names. "
46
+ "Copy the example config there and list your account binaries."
47
+ )
48
+ HOST_PROFILE_FAILURE_TEXT: str = "session model alias carries no host profile"
49
+ MINT_TIMEOUT_SECONDS: int = 1
50
+ SERVED_COMMAND_NAME: str = "claude"
51
+ REVIEW_BINARY_REFUSAL_TEXT: str = (
52
+ "--dangerously-skip-permissions cannot be used with root privileges"
53
+ )
54
+ REVIEW_FAILURE_RETURNCODE: int = 1
55
+ EMPTY_REVIEW_STDOUT: str = ""
56
+ ROOT_USER_ID: int = 0
57
+ UNPRIVILEGED_USER_ID: int = 1000
58
+
59
+
60
+ def _serve_a_refusing_binary(
61
+ *_all_positional: object, **_all_keyword: object
62
+ ) -> ChainInvocationOutcome:
63
+ return ChainInvocationOutcome(
64
+ served_command=SERVED_COMMAND_NAME,
65
+ returncode=REVIEW_FAILURE_RETURNCODE,
66
+ stdout=EMPTY_REVIEW_STDOUT,
67
+ stderr=REVIEW_BINARY_REFUSAL_TEXT,
68
+ attempts=(),
69
+ )
70
+
71
+
72
+ def test_failed_review_reports_what_the_served_binary_wrote(
73
+ monkeypatch: pytest.MonkeyPatch,
74
+ tmp_path: Path,
75
+ capsys: pytest.CaptureFixture[str],
76
+ ) -> None:
77
+ """A review that a served binary refused names the refusal.
78
+
79
+ The binary can decline for reasons the caller must act on, such as a
80
+ permission mode it will not accept. Dropping its words leaves a bare
81
+ exit code, and no stamp is minted either way.
82
+ """
83
+ monkeypatch.setattr(
84
+ invoker, "_run_claude_with_empty_stdin", _serve_a_refusing_binary
85
+ )
86
+
87
+ outcome = invoker._run_chain_review(
88
+ working_directory=tmp_path,
89
+ timeout_seconds=MINT_TIMEOUT_SECONDS,
90
+ effort=DEFAULT_CODE_REVIEW_EFFORT,
91
+ )
92
+
93
+ captured_streams = capsys.readouterr()
94
+ assert outcome.returncode == REVIEW_FAILURE_RETURNCODE
95
+ assert REVIEW_BINARY_REFUSAL_TEXT in captured_streams.err
96
+
97
+
98
+ def test_missing_chain_config_reports_its_remedy_on_stderr(
99
+ monkeypatch: pytest.MonkeyPatch,
100
+ tmp_path: Path,
101
+ capsys: pytest.CaptureFixture[str],
102
+ ) -> None:
103
+ def raise_chain_configuration_error(**_all_keyword_arguments: object) -> None:
104
+ raise ChainConfigurationError(CHAIN_CONFIG_REMEDY_TEXT)
105
+
106
+ monkeypatch.setattr(
107
+ invoker, "invoke_code_review_and_record_stamp", raise_chain_configuration_error
108
+ )
109
+
110
+ outcome = invoker._mint_or_config_outcome(
111
+ working_directory=tmp_path,
112
+ session_model=FIXTURE_SESSION_OPUS,
113
+ timeout_seconds=MINT_TIMEOUT_SECONDS,
114
+ effort=DEFAULT_CODE_REVIEW_EFFORT,
115
+ )
116
+
117
+ captured_streams = capsys.readouterr()
118
+ assert outcome.is_stamp_minted is False
119
+ assert CHAIN_CONFIG_REMEDY_TEXT in captured_streams.err
120
+
121
+
122
+ def test_host_profile_failure_reports_its_reason_on_stderr(
123
+ monkeypatch: pytest.MonkeyPatch,
124
+ tmp_path: Path,
125
+ capsys: pytest.CaptureFixture[str],
126
+ ) -> None:
127
+ def raise_host_profile_error(**_all_keyword_arguments: object) -> None:
128
+ raise ValueError(HOST_PROFILE_FAILURE_TEXT)
129
+
130
+ monkeypatch.setattr(
131
+ invoker, "invoke_code_review_and_record_stamp", raise_host_profile_error
132
+ )
133
+
134
+ outcome = invoker._mint_or_config_outcome(
135
+ working_directory=tmp_path,
136
+ session_model=FIXTURE_SESSION_OPUS,
137
+ timeout_seconds=MINT_TIMEOUT_SECONDS,
138
+ effort=DEFAULT_CODE_REVIEW_EFFORT,
139
+ )
140
+
141
+ captured_streams = capsys.readouterr()
142
+ assert outcome.is_stamp_minted is False
143
+ assert HOST_PROFILE_FAILURE_TEXT in captured_streams.err
@@ -19,7 +19,7 @@ from _code_review_test_support import (
19
19
  from dev_env_scripts_constants.code_review_constants import (
20
20
  CODE_REVIEW_MODEL_ALIAS,
21
21
  DEFAULT_CODE_REVIEW_EFFORT,
22
- PERMISSION_MODE_BYPASS,
22
+ REVIEW_PERMISSION_MODE as PERMISSION_MODE_BYPASS,
23
23
  PERMISSION_MODE_FLAG,
24
24
  )
25
25
  from dev_env_scripts_constants.grok_worker_constants import (
@@ -33,7 +33,7 @@ from dev_env_scripts_constants.code_review_constants import (
33
33
  MAXIMUM_STAMP_MINT_PASSES,
34
34
  MODE_CHAIN,
35
35
  MODE_IN_SESSION,
36
- PERMISSION_MODE_BYPASS,
36
+ REVIEW_PERMISSION_MODE as PERMISSION_MODE_BYPASS,
37
37
  PERMISSION_MODE_FLAG,
38
38
  RESULT_KEY_BOUND_HASH,
39
39
  RESULT_KEY_DIRTY_TREE,
@@ -35,17 +35,20 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
35
35
  CLI_TIMEOUT_FLAG,
36
36
  CLASSIFICATION_AUTH_FAILURE,
37
37
  CLASSIFICATION_OK,
38
+ CLASSIFICATION_TIMEOUT,
38
39
  CLASSIFICATION_USAGE_LIMIT,
39
40
  CWD_FLAG,
40
41
  DEFAULT_ROLE,
41
- DEFAULT_SPAWN_MAX_TURNS,
42
42
  DEFAULT_WORKER_TIMEOUT_SECONDS,
43
+ MAXIMUM_WORKER_TIMEOUT_SECONDS,
44
+ MIN_WORKER_TIMEOUT_SECONDS,
43
45
  OUTPUT_FORMAT_FLAG,
44
46
  OUTPUT_FORMAT_JSON,
45
47
  PROMPT_FILE_FLAG,
46
48
  REASON_CLAUDE_AGENT_REQUIRED,
47
49
  REASON_GROK_AUTH_FAILED,
48
50
  REASON_PROMPT_FILE_MISSING,
51
+ REASON_TIMEOUT_OUT_OF_BOUNDS,
49
52
  RESULT_KEY_ATTEMPTS,
50
53
  RESULT_KEY_OK,
51
54
  RESULT_KEY_OUTPUT,
@@ -59,11 +62,16 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
59
62
  TIER_CLAUDE_HEADLESS,
60
63
  TIER_GROK,
61
64
  )
62
- from grok_headless_runner import GrokRunnerOutcome # noqa: E402
65
+ import grok_headless_runner # noqa: E402
66
+ from grok_headless_runner import ( # noqa: E402
67
+ GrokRunnerOutcome,
68
+ run_headless_worker,
69
+ )
63
70
  from grok_worker_preflight import PreflightOutcome # noqa: E402
64
71
 
65
72
  HOST_PROFILE_CLAUDE = "Claude"
66
73
  HOST_PROFILE_THIRD_PARTY = "ThirdParty"
74
+ NON_POSITIVE_TIMEOUT_SECONDS = 0
67
75
 
68
76
  FIXTURE_GROK_STDOUT = '{"tier":"grok","status":"done"}'
69
77
  FIXTURE_CLAUDE_STDOUT = '{"tier":"claude","status":"done"}'
@@ -72,6 +80,7 @@ FIXTURE_GROK_RETURNCODE = 0
72
80
  FIXTURE_CLAUDE_RETURNCODE = 0
73
81
  FIXTURE_FAILED_RETURNCODE = 1
74
82
  FIXTURE_ROLE = "code-quality-agent"
83
+ MIN_WORKER_TIMEOUT_SECONDS_CONSTANT_NAME = "MIN_WORKER_TIMEOUT_SECONDS"
75
84
  LARGE_PROMPT_CHARACTER_COUNT = 40000
76
85
  WINDOWS_SAFE_ARGV_ELEMENT_CEILING = 8192
77
86
  EXPECTED_PRIMARY_AGENT_FOR_DEFAULT_ROLE = Path(
@@ -261,7 +270,6 @@ def test_grok_ok_serves_tier_one(
261
270
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
262
271
  is_claude_tier_enabled=False,
263
272
  run_state_directory=run_state_directory,
264
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
265
273
  )
266
274
 
267
275
  assert spawn_outcome.is_ok is True
@@ -297,7 +305,6 @@ def test_grok_usage_limited_on_claude_host_requires_agent(
297
305
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
298
306
  is_claude_tier_enabled=False,
299
307
  run_state_directory=run_state_directory,
300
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
301
308
  )
302
309
 
303
310
  assert spawn_outcome.is_ok is False
@@ -313,6 +320,128 @@ def test_grok_usage_limited_on_claude_host_requires_agent(
313
320
  assert spawn_outcome.all_attempts[1].reason == REASON_CLAUDE_AGENT_REQUIRED
314
321
 
315
322
 
323
+ def test_out_of_bounds_timeout_is_reported_as_config_not_worker_failure(
324
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
325
+ ) -> None:
326
+ """A refused timeout prints a structured outcome and exits 3, never a traceback.
327
+
328
+ ::
329
+
330
+ --timeout-seconds 0 ok: reason timeout_out_of_bounds, exit 3
331
+ --timeout-seconds 5401 ok: reason timeout_out_of_bounds, exit 3
332
+ """
333
+ prompt_file, working_directory, run_state_directory = _paths(tmp_path)
334
+ _install_seams(monkeypatch, grok_outcome=_grok_ok())
335
+ monkeypatch.setattr(
336
+ dispatcher, "spawn_grok_runner", grok_headless_runner.run_headless_worker
337
+ )
338
+
339
+ for each_refused_timeout in (
340
+ NON_POSITIVE_TIMEOUT_SECONDS,
341
+ MAXIMUM_WORKER_TIMEOUT_SECONDS + 1,
342
+ ):
343
+ exit_code = dispatcher.main(
344
+ [
345
+ CLI_ROLE_FLAG,
346
+ FIXTURE_ROLE,
347
+ PROMPT_FILE_FLAG,
348
+ str(prompt_file),
349
+ CWD_FLAG,
350
+ str(working_directory),
351
+ CLI_TIMEOUT_FLAG,
352
+ str(each_refused_timeout),
353
+ CLI_RUN_STATE_DIR_FLAG,
354
+ str(run_state_directory),
355
+ ]
356
+ )
357
+ parsed_payload = json.loads(capsys.readouterr().out)
358
+ all_attempt_reasons = [
359
+ each_attempt[ATTEMPT_KEY_REASON]
360
+ for each_attempt in parsed_payload[RESULT_KEY_ATTEMPTS]
361
+ ]
362
+
363
+ assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE, each_refused_timeout
364
+ assert parsed_payload[RESULT_KEY_OK] is False, each_refused_timeout
365
+ assert parsed_payload[RESULT_KEY_TIER_USED] is None, each_refused_timeout
366
+ assert REASON_TIMEOUT_OUT_OF_BOUNDS in all_attempt_reasons, each_refused_timeout
367
+
368
+
369
+ def test_out_of_bounds_timeout_is_refused_when_the_grok_tier_is_unreachable(
370
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
371
+ ) -> None:
372
+ """The bounds hold on a host whose preflight never reaches the grok runner.
373
+
374
+ ::
375
+
376
+ preflight unusable, --timeout-seconds 5401
377
+ ok: reason timeout_out_of_bounds, exit 3, no tier invoked
378
+ """
379
+ prompt_file, working_directory, run_state_directory = _paths(tmp_path)
380
+ call_log = _install_seams(
381
+ monkeypatch,
382
+ preflight_outcome=_fallthrough_preflight(REASON_GROK_AUTH_FAILED),
383
+ claude_outcome=_claude_served(),
384
+ )
385
+
386
+ exit_code = dispatcher.main(
387
+ [
388
+ CLI_ROLE_FLAG,
389
+ FIXTURE_ROLE,
390
+ PROMPT_FILE_FLAG,
391
+ str(prompt_file),
392
+ CWD_FLAG,
393
+ str(working_directory),
394
+ CLI_TIMEOUT_FLAG,
395
+ str(MAXIMUM_WORKER_TIMEOUT_SECONDS + 1),
396
+ CLI_RUN_STATE_DIR_FLAG,
397
+ str(run_state_directory),
398
+ ]
399
+ )
400
+ parsed_payload = json.loads(capsys.readouterr().out)
401
+ all_attempt_reasons = [
402
+ each_attempt[ATTEMPT_KEY_REASON]
403
+ for each_attempt in parsed_payload[RESULT_KEY_ATTEMPTS]
404
+ ]
405
+
406
+ assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE
407
+ assert REASON_TIMEOUT_OUT_OF_BOUNDS in all_attempt_reasons
408
+ assert call_log.preflight_calls == 0
409
+ assert call_log.claude_calls == 0
410
+
411
+
412
+ def test_timed_out_grok_worker_is_recorded_as_timeout_not_served(
413
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
414
+ ) -> None:
415
+ """A killed worker reaches the dispatcher's trail as timeout, never as served.
416
+
417
+ ::
418
+
419
+ grok tier times out ok: attempt reason timeout, tier_used moves on
420
+ """
421
+ prompt_file, working_directory, run_state_directory = _paths(tmp_path)
422
+ call_log = _install_seams(
423
+ monkeypatch,
424
+ grok_outcome=_grok_failure(CLASSIFICATION_TIMEOUT),
425
+ claude_outcome=_claude_served(),
426
+ host_profile=HOST_PROFILE_THIRD_PARTY,
427
+ )
428
+
429
+ spawn_outcome = dispatcher.resolve_worker_spawn(
430
+ role=FIXTURE_ROLE,
431
+ prompt_file=prompt_file,
432
+ working_directory=working_directory,
433
+ timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
434
+ is_claude_tier_enabled=False,
435
+ run_state_directory=run_state_directory,
436
+ )
437
+
438
+ assert call_log.grok_calls == 1
439
+ assert spawn_outcome.all_attempts[0].tier == TIER_GROK
440
+ assert spawn_outcome.all_attempts[0].is_ok is False
441
+ assert spawn_outcome.all_attempts[0].reason == CLASSIFICATION_TIMEOUT
442
+ assert spawn_outcome.tier_used != TIER_GROK
443
+
444
+
316
445
  def test_grok_auth_failed_on_third_party_runs_tier_three(
317
446
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
318
447
  ) -> None:
@@ -331,7 +460,6 @@ def test_grok_auth_failed_on_third_party_runs_tier_three(
331
460
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
332
461
  is_claude_tier_enabled=False,
333
462
  run_state_directory=run_state_directory,
334
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
335
463
  )
336
464
 
337
465
  assert spawn_outcome.is_ok is True
@@ -423,6 +551,40 @@ def test_config_error_returns_exit_three(
423
551
  assert parsed_payload[RESULT_KEY_RETURNCODE] == SPAWN_CONFIG_ERROR_EXIT_CODE
424
552
 
425
553
 
554
+ def test_below_floor_timeout_returns_json_config_exit(
555
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
556
+ ) -> None:
557
+ """A below-floor timeout reads as a config error, not an escaping traceback."""
558
+ prompt_file, working_directory, run_state_directory = _paths(tmp_path)
559
+ _install_seams(monkeypatch, grok_outcome=_grok_ok())
560
+ monkeypatch.setattr(dispatcher, "spawn_grok_runner", run_headless_worker)
561
+ below_floor_timeout_seconds = MIN_WORKER_TIMEOUT_SECONDS - 1
562
+
563
+ exit_code = dispatcher.main(
564
+ [
565
+ CLI_ROLE_FLAG,
566
+ FIXTURE_ROLE,
567
+ PROMPT_FILE_FLAG,
568
+ str(prompt_file),
569
+ CWD_FLAG,
570
+ str(working_directory),
571
+ CLI_TIMEOUT_FLAG,
572
+ str(below_floor_timeout_seconds),
573
+ CLI_RUN_STATE_DIR_FLAG,
574
+ str(run_state_directory),
575
+ ]
576
+ )
577
+
578
+ assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE
579
+ captured = capsys.readouterr()
580
+ assert captured.err == ""
581
+ parsed_payload = json.loads(captured.out)
582
+ assert parsed_payload[RESULT_KEY_OK] is False
583
+ assert parsed_payload[RESULT_KEY_TIER_USED] is None
584
+ assert parsed_payload[RESULT_KEY_RETURNCODE] == SPAWN_CONFIG_ERROR_EXIT_CODE
585
+ assert MIN_WORKER_TIMEOUT_SECONDS_CONSTANT_NAME in parsed_payload[RESULT_KEY_OUTPUT]
586
+
587
+
426
588
  def test_attempts_array_ordering_across_tiers(
427
589
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
428
590
  ) -> None:
@@ -441,7 +603,6 @@ def test_attempts_array_ordering_across_tiers(
441
603
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
442
604
  is_claude_tier_enabled=True,
443
605
  run_state_directory=run_state_directory,
444
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
445
606
  )
446
607
 
447
608
  all_tiers = [each_attempt.tier for each_attempt in spawn_outcome.all_attempts]
@@ -515,7 +676,6 @@ def test_preflight_fallthrough_skips_grok_runner(
515
676
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
516
677
  is_claude_tier_enabled=False,
517
678
  run_state_directory=run_state_directory,
518
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
519
679
  )
520
680
 
521
681
  assert call_log.grok_calls == 0
@@ -533,9 +693,15 @@ def test_detect_host_profile_is_consumed_not_reimplemented() -> None:
533
693
  assert "THIRD_PARTY" not in source_text
534
694
 
535
695
 
536
- def test_default_max_turns_reaches_grok_kwargs(
696
+ def test_dispatcher_grok_invocation_carries_no_turn_cap(
537
697
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
538
698
  ) -> None:
699
+ """The dispatcher hands the runner no turn cap; the timeout is the only bound.
700
+
701
+ ::
702
+
703
+ grok tier kwargs ok: timeout_seconds present, max_turns absent
704
+ """
539
705
  prompt_file, working_directory, run_state_directory = _paths(tmp_path)
540
706
  call_log = _install_seams(
541
707
  monkeypatch,
@@ -559,7 +725,10 @@ def test_default_max_turns_reaches_grok_kwargs(
559
725
 
560
726
  assert exit_code == SPAWN_SERVED_EXIT_CODE
561
727
  assert call_log.grok_keyword_arguments is not None
562
- assert call_log.grok_keyword_arguments["max_turns"] == DEFAULT_SPAWN_MAX_TURNS
728
+ assert "max_turns" not in call_log.grok_keyword_arguments
729
+ assert call_log.grok_keyword_arguments["timeout_seconds"] == (
730
+ DEFAULT_WORKER_TIMEOUT_SECONDS
731
+ )
563
732
  parsed_payload = json.loads(capsys.readouterr().out)
564
733
  assert parsed_payload[RESULT_KEY_OK] is True
565
734
 
@@ -645,7 +814,6 @@ def test_default_role_maps_to_primary_agent_stem(
645
814
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
646
815
  is_claude_tier_enabled=False,
647
816
  run_state_directory=run_state_directory,
648
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
649
817
  )
650
818
 
651
819
  assert spawn_outcome.is_ok is True
@@ -674,7 +842,6 @@ def test_tier_three_argv_includes_agent_for_default_role(
674
842
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
675
843
  is_claude_tier_enabled=False,
676
844
  run_state_directory=run_state_directory,
677
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
678
845
  )
679
846
 
680
847
  assert spawn_outcome.tier_used == TIER_CLAUDE_HEADLESS
@@ -708,7 +875,6 @@ def test_large_prompt_stays_out_of_claude_argv(
708
875
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
709
876
  is_claude_tier_enabled=False,
710
877
  run_state_directory=run_state_directory,
711
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
712
878
  )
713
879
 
714
880
  assert spawn_outcome.tier_used == TIER_CLAUDE_HEADLESS
@@ -926,8 +1092,7 @@ def test_headless_chain_runner_lock_serializes_distinct_cwds(
926
1092
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
927
1093
  is_claude_tier_enabled=False,
928
1094
  run_state_directory=run_state_directory,
929
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
930
- )
1095
+ )
931
1096
  except (OSError, RuntimeError, ValueError, AssertionError) as raised_error:
932
1097
  all_errors.append(raised_error)
933
1098
 
@@ -1005,7 +1170,6 @@ def test_usage_limit_fallover_delivers_full_prompt_to_each_binary(
1005
1170
  timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
1006
1171
  is_claude_tier_enabled=False,
1007
1172
  run_state_directory=run_state_directory,
1008
- max_turns=DEFAULT_SPAWN_MAX_TURNS,
1009
1173
  )
1010
1174
 
1011
1175
  assert prompt_text_by_command["claude"] == FIXTURE_PROMPT_TEXT