@pushpalsdev/cli 1.1.34 → 1.1.35
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/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +107 -5
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +90 -2
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +35 -3
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +0 -2
package/package.json
CHANGED
|
@@ -116,6 +116,8 @@ _WEB_REVIEW_NO_EDIT_WATCHDOG_S = 240
|
|
|
116
116
|
_BACKGROUND_NO_EDIT_WATCHDOG_S = 120
|
|
117
117
|
_NO_EDIT_RECOVERY_WATCHDOG_S = 90
|
|
118
118
|
_DEFAULT_NO_EDIT_RECHECK_S = 120
|
|
119
|
+
_DEFAULT_STARTUP_STALL_WATCHDOG_S = 210
|
|
120
|
+
_RECOVERY_STARTUP_STALL_WATCHDOG_S = 150
|
|
119
121
|
_DEFAULT_ROLLOUT_WATCHDOG_S = 300
|
|
120
122
|
_SMALL_TASK_ROLLOUT_WATCHDOG_S = 240
|
|
121
123
|
_NARROW_TEST_TASK_ROLLOUT_WATCHDOG_S = 150
|
|
@@ -755,6 +757,44 @@ def _resolve_no_edit_recheck_seconds(communicate_timeout_s: Optional[int]) -> in
|
|
|
755
757
|
return max(1, min(_DEFAULT_NO_EDIT_RECHECK_S, upper))
|
|
756
758
|
|
|
757
759
|
|
|
760
|
+
def _resolve_startup_stall_watchdog_seconds(
|
|
761
|
+
communicate_timeout_s: Optional[int],
|
|
762
|
+
recovery_attempt: int = 0,
|
|
763
|
+
) -> Optional[int]:
|
|
764
|
+
if not communicate_timeout_s:
|
|
765
|
+
return None
|
|
766
|
+
|
|
767
|
+
raw = os.environ.get("WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S", "").strip()
|
|
768
|
+
if raw:
|
|
769
|
+
if raw == "0":
|
|
770
|
+
return None
|
|
771
|
+
parsed = _to_positive_int(raw)
|
|
772
|
+
if parsed is None:
|
|
773
|
+
log.info(
|
|
774
|
+
"Invalid WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S="
|
|
775
|
+
f"{raw!r}; using default startup-stall watchdog."
|
|
776
|
+
)
|
|
777
|
+
else:
|
|
778
|
+
return max(1, min(parsed, max(1, communicate_timeout_s - 1)))
|
|
779
|
+
|
|
780
|
+
default_s = (
|
|
781
|
+
_RECOVERY_STARTUP_STALL_WATCHDOG_S
|
|
782
|
+
if recovery_attempt > 0
|
|
783
|
+
else _DEFAULT_STARTUP_STALL_WATCHDOG_S
|
|
784
|
+
)
|
|
785
|
+
floor_s = 60
|
|
786
|
+
return max(floor_s, min(default_s, max(floor_s, communicate_timeout_s - 1)))
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _startup_stall_recovery_model(current_model: str) -> str:
|
|
790
|
+
normalized = str(current_model or "").strip()
|
|
791
|
+
if not normalized:
|
|
792
|
+
return LEGACY_CODEX_MODEL_FALLBACK
|
|
793
|
+
if normalized.lower() == LEGACY_CODEX_MODEL_FALLBACK.lower():
|
|
794
|
+
return normalized
|
|
795
|
+
return LEGACY_CODEX_MODEL_FALLBACK
|
|
796
|
+
|
|
797
|
+
|
|
758
798
|
def _looks_like_web_review_prompt(prompt: str) -> bool:
|
|
759
799
|
text = str(prompt or "").lower()
|
|
760
800
|
return "repo-native web review" in text or "web review path" in text
|
|
@@ -2337,6 +2377,15 @@ def _run_codex_task(
|
|
|
2337
2377
|
else None
|
|
2338
2378
|
)
|
|
2339
2379
|
no_edit_recheck_s = _resolve_no_edit_recheck_seconds(communicate_timeout_s)
|
|
2380
|
+
startup_stall_watchdog_s = _resolve_startup_stall_watchdog_seconds(
|
|
2381
|
+
communicate_timeout_s,
|
|
2382
|
+
recovery_attempt=startup_stall_recovery_attempt,
|
|
2383
|
+
)
|
|
2384
|
+
startup_stall_deadline = (
|
|
2385
|
+
started_at + float(startup_stall_watchdog_s)
|
|
2386
|
+
if startup_stall_watchdog_s is not None
|
|
2387
|
+
else None
|
|
2388
|
+
)
|
|
2340
2389
|
rollout_watchdog_s = (
|
|
2341
2390
|
_resolve_rollout_watchdog_seconds(
|
|
2342
2391
|
prompt,
|
|
@@ -2364,9 +2413,50 @@ def _run_codex_task(
|
|
|
2364
2413
|
_terminate_active_child()
|
|
2365
2414
|
break
|
|
2366
2415
|
|
|
2416
|
+
if startup_stall_deadline is not None and now >= startup_stall_deadline:
|
|
2417
|
+
with trace_lock:
|
|
2418
|
+
live_trace = dict(stdout_trace_state)
|
|
2419
|
+
summaries = stdout_trace_state.get("summaries")
|
|
2420
|
+
if isinstance(summaries, list):
|
|
2421
|
+
live_trace["summaries"] = list(summaries)
|
|
2422
|
+
if _codex_trace_is_startup_stall(live_trace):
|
|
2423
|
+
changed_paths, _, effective_paths = _codex_changed_paths(repo, baseline_snapshot)
|
|
2424
|
+
if not effective_paths:
|
|
2425
|
+
no_edit_artifact_only_paths = _describe_non_publishable_paths(
|
|
2426
|
+
changed_paths,
|
|
2427
|
+
baseline_snapshot,
|
|
2428
|
+
)
|
|
2429
|
+
no_edit_watchdog_fired = True
|
|
2430
|
+
elapsed_s = int(max(0.0, now - started_at))
|
|
2431
|
+
log.info(
|
|
2432
|
+
f"Startup-stall watchdog fired after {elapsed_s}s with no assistant/tool progress."
|
|
2433
|
+
)
|
|
2434
|
+
_terminate_active_child()
|
|
2435
|
+
break
|
|
2436
|
+
startup_stall_deadline = None
|
|
2437
|
+
|
|
2367
2438
|
if no_edit_deadline is not None and now >= no_edit_deadline:
|
|
2368
2439
|
changed_paths, _, effective_paths = _codex_changed_paths(repo, baseline_snapshot)
|
|
2369
2440
|
if not effective_paths:
|
|
2441
|
+
with trace_lock:
|
|
2442
|
+
live_trace = dict(stdout_trace_state)
|
|
2443
|
+
summaries = stdout_trace_state.get("summaries")
|
|
2444
|
+
if isinstance(summaries, list):
|
|
2445
|
+
live_trace["summaries"] = list(summaries)
|
|
2446
|
+
startup_only = _codex_trace_is_startup_stall(live_trace)
|
|
2447
|
+
if (
|
|
2448
|
+
startup_only
|
|
2449
|
+
and startup_stall_deadline is not None
|
|
2450
|
+
and now < startup_stall_deadline
|
|
2451
|
+
):
|
|
2452
|
+
no_edit_deadline = startup_stall_deadline
|
|
2453
|
+
remaining_s = int(max(1.0, startup_stall_deadline - now))
|
|
2454
|
+
log.info(
|
|
2455
|
+
"No-edit watchdog observed only Codex startup events; "
|
|
2456
|
+
f"allowing {remaining_s}s for first assistant/tool progress "
|
|
2457
|
+
"before startup-stall recovery."
|
|
2458
|
+
)
|
|
2459
|
+
continue
|
|
2370
2460
|
no_edit_artifact_only_paths = _describe_non_publishable_paths(
|
|
2371
2461
|
changed_paths,
|
|
2372
2462
|
baseline_snapshot,
|
|
@@ -2377,9 +2467,15 @@ def _run_codex_task(
|
|
|
2377
2467
|
if no_edit_artifact_only_paths
|
|
2378
2468
|
else ""
|
|
2379
2469
|
)
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2470
|
+
if startup_only:
|
|
2471
|
+
elapsed_s = int(max(0.0, now - started_at))
|
|
2472
|
+
log.info(
|
|
2473
|
+
f"Startup-stall watchdog fired after {elapsed_s}s with no assistant/tool progress."
|
|
2474
|
+
)
|
|
2475
|
+
else:
|
|
2476
|
+
log.info(
|
|
2477
|
+
f"No-edit watchdog fired after {int(no_edit_watchdog_s or 0)}s with no publishable file changes.{artifact_detail} Retrying with patch-first guidance."
|
|
2478
|
+
)
|
|
2383
2479
|
_terminate_active_child()
|
|
2384
2480
|
break
|
|
2385
2481
|
no_edit_deadline = now + float(no_edit_recheck_s)
|
|
@@ -2550,9 +2646,15 @@ def _run_codex_task(
|
|
|
2550
2646
|
*supplemental_guidance,
|
|
2551
2647
|
_build_startup_stall_recovery_guidance(trace_excerpt),
|
|
2552
2648
|
]
|
|
2649
|
+
recovery_model = _startup_stall_recovery_model(model)
|
|
2650
|
+
recovery_detail = (
|
|
2651
|
+
f" using fallback model {recovery_model!r}"
|
|
2652
|
+
if recovery_model and recovery_model != model
|
|
2653
|
+
else ""
|
|
2654
|
+
)
|
|
2553
2655
|
log.warning(
|
|
2554
2656
|
"Codex emitted only startup events before the no-edit watchdog; "
|
|
2555
|
-
"restarting Codex once before classifying the job terminally."
|
|
2657
|
+
f"restarting Codex once{recovery_detail} before classifying the job terminally."
|
|
2556
2658
|
)
|
|
2557
2659
|
retry_result = _run_codex_task(
|
|
2558
2660
|
repo,
|
|
@@ -2563,7 +2665,7 @@ def _run_codex_task(
|
|
|
2563
2665
|
startup_stall_recovery_attempt=startup_stall_recovery_attempt + 1,
|
|
2564
2666
|
no_edit_recovery_attempt=no_edit_recovery_attempt,
|
|
2565
2667
|
rollout_recovery_attempt=rollout_recovery_attempt,
|
|
2566
|
-
model_override=model_override,
|
|
2668
|
+
model_override=recovery_model or model_override,
|
|
2567
2669
|
baseline_changes=baseline_snapshot,
|
|
2568
2670
|
)
|
|
2569
2671
|
retry_result["usage"] = _merge_usage_records(usage, retry_result.get("usage"))
|
|
@@ -49,6 +49,7 @@ from openai_codex_executor import (
|
|
|
49
49
|
_resolve_codex_command_prefix,
|
|
50
50
|
_resolve_no_edit_watchdog_seconds,
|
|
51
51
|
_resolve_rollout_watchdog_seconds,
|
|
52
|
+
_resolve_startup_stall_watchdog_seconds,
|
|
52
53
|
_unwrap_shell_wrapper_command,
|
|
53
54
|
_usage_from_trace_or_estimate,
|
|
54
55
|
)
|
|
@@ -372,6 +373,63 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
372
373
|
self.assertEqual(task.repo, str(repo.resolve()))
|
|
373
374
|
self.assertEqual(task.instruction, "Make one small publishable change")
|
|
374
375
|
|
|
376
|
+
def test_parse_payload_accepts_positional_payload_file_path(self) -> None:
|
|
377
|
+
with tempfile.TemporaryDirectory(prefix="pushpals-payload-file-positional-") as temp_dir:
|
|
378
|
+
repo = Path(temp_dir) / "repo"
|
|
379
|
+
repo.mkdir(parents=True, exist_ok=True)
|
|
380
|
+
payload = {
|
|
381
|
+
"kind": "task.execute",
|
|
382
|
+
"repo": str(repo),
|
|
383
|
+
"params": {"instruction": "Recover from a direct-worker payload handoff"},
|
|
384
|
+
}
|
|
385
|
+
encoded = base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")
|
|
386
|
+
payload_file = Path(temp_dir) / "payload.b64"
|
|
387
|
+
payload_file.write_text(encoded, encoding="utf-8")
|
|
388
|
+
|
|
389
|
+
task = parse_task_execute_payload(
|
|
390
|
+
["executor", str(payload_file)],
|
|
391
|
+
logger=Logger("[test]"),
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
self.assertEqual(task.kind, "task.execute")
|
|
395
|
+
self.assertEqual(task.repo, str(repo.resolve()))
|
|
396
|
+
self.assertEqual(task.instruction, "Recover from a direct-worker payload handoff")
|
|
397
|
+
|
|
398
|
+
def test_parse_payload_accepts_unpadded_base64_payload(self) -> None:
|
|
399
|
+
with tempfile.TemporaryDirectory(prefix="pushpals-payload-unpadded-") as temp_dir:
|
|
400
|
+
repo = Path(temp_dir) / "repo"
|
|
401
|
+
repo.mkdir(parents=True, exist_ok=True)
|
|
402
|
+
payload = {
|
|
403
|
+
"kind": "task.execute",
|
|
404
|
+
"repo": str(repo),
|
|
405
|
+
"params": {"instruction": "Accept wrapper-normalized payload padding"},
|
|
406
|
+
}
|
|
407
|
+
encoded = base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")
|
|
408
|
+
unpadded = encoded.rstrip("=")
|
|
409
|
+
|
|
410
|
+
task = parse_task_execute_payload(["executor", unpadded], logger=Logger("[test]"))
|
|
411
|
+
|
|
412
|
+
self.assertEqual(task.kind, "task.execute")
|
|
413
|
+
self.assertEqual(task.repo, str(repo.resolve()))
|
|
414
|
+
self.assertEqual(task.instruction, "Accept wrapper-normalized payload padding")
|
|
415
|
+
|
|
416
|
+
def test_parse_payload_accepts_raw_json_payload(self) -> None:
|
|
417
|
+
with tempfile.TemporaryDirectory(prefix="pushpals-payload-raw-json-") as temp_dir:
|
|
418
|
+
repo = Path(temp_dir) / "repo"
|
|
419
|
+
repo.mkdir(parents=True, exist_ok=True)
|
|
420
|
+
payload = {
|
|
421
|
+
"kind": "task.execute",
|
|
422
|
+
"repo": str(repo),
|
|
423
|
+
"params": {"instruction": "Accept raw JSON from a recovery wrapper"},
|
|
424
|
+
}
|
|
425
|
+
raw_json = json.dumps(payload)
|
|
426
|
+
|
|
427
|
+
task = parse_task_execute_payload(["executor", raw_json], logger=Logger("[test]"))
|
|
428
|
+
|
|
429
|
+
self.assertEqual(task.kind, "task.execute")
|
|
430
|
+
self.assertEqual(task.repo, str(repo.resolve()))
|
|
431
|
+
self.assertEqual(task.instruction, "Accept raw JSON from a recovery wrapper")
|
|
432
|
+
|
|
375
433
|
def test_parse_payload_prefers_helper_tests_for_visual_derivation_tasks(self) -> None:
|
|
376
434
|
with tempfile.TemporaryDirectory(prefix="pushpals-visual-guidance-") as temp_dir:
|
|
377
435
|
repo = Path(temp_dir) / "repo"
|
|
@@ -1091,13 +1149,16 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
1091
1149
|
"",
|
|
1092
1150
|
"argv = sys.argv[1:]",
|
|
1093
1151
|
"last_message_path = None",
|
|
1152
|
+
"model = ''",
|
|
1094
1153
|
"for index, arg in enumerate(argv):",
|
|
1095
1154
|
" if arg == '--output-last-message' and index + 1 < len(argv):",
|
|
1096
1155
|
" last_message_path = argv[index + 1]",
|
|
1156
|
+
" if arg == '-m' and index + 1 < len(argv):",
|
|
1157
|
+
" model = argv[index + 1]",
|
|
1097
1158
|
" break",
|
|
1098
1159
|
"",
|
|
1099
1160
|
"prompt = sys.stdin.read()",
|
|
1100
|
-
"if 'Codex startup-stall recovery' in prompt:",
|
|
1161
|
+
"if 'Codex startup-stall recovery' in prompt and model == 'gpt-5.4':",
|
|
1101
1162
|
" Path('src').mkdir(exist_ok=True)",
|
|
1102
1163
|
" Path('src/startup-stall-recovered.txt').write_text('patched after restart\\n', encoding='utf-8')",
|
|
1103
1164
|
" if last_message_path:",
|
|
@@ -1119,7 +1180,8 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
1119
1180
|
"OPENAI_API_KEY": "pushpals-startup-stall-test-key",
|
|
1120
1181
|
"WORKERPALS_OPENAI_CODEX_JSON": "true",
|
|
1121
1182
|
"WORKERPALS_OPENAI_CODEX_TIMEOUT_S": "20",
|
|
1122
|
-
"WORKERPALS_OPENAI_CODEX_NO_EDIT_WATCHDOG_S": "
|
|
1183
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_WATCHDOG_S": "0",
|
|
1184
|
+
"WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S": "1",
|
|
1123
1185
|
"WORKERPALS_OPENAI_CODEX_PROGRESS_LOG_INTERVAL_S": "1",
|
|
1124
1186
|
}
|
|
1125
1187
|
with mock.patch.dict(os.environ, env_overrides, clear=False):
|
|
@@ -1189,6 +1251,7 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
1189
1251
|
"WORKERPALS_OPENAI_CODEX_JSON": "true",
|
|
1190
1252
|
"WORKERPALS_OPENAI_CODEX_TIMEOUT_S": "20",
|
|
1191
1253
|
"WORKERPALS_OPENAI_CODEX_NO_EDIT_WATCHDOG_S": "1",
|
|
1254
|
+
"WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S": "1",
|
|
1192
1255
|
"WORKERPALS_OPENAI_CODEX_PROGRESS_LOG_INTERVAL_S": "1",
|
|
1193
1256
|
}
|
|
1194
1257
|
with mock.patch.dict(os.environ, env_overrides, clear=False):
|
|
@@ -1587,6 +1650,31 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
1587
1650
|
|
|
1588
1651
|
self.assertEqual(watchdog_s, 180)
|
|
1589
1652
|
|
|
1653
|
+
def test_startup_stall_watchdog_allows_slower_first_response_than_no_edit_watchdog(self) -> None:
|
|
1654
|
+
with mock.patch.dict(
|
|
1655
|
+
os.environ,
|
|
1656
|
+
{"WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S": ""},
|
|
1657
|
+
clear=False,
|
|
1658
|
+
):
|
|
1659
|
+
watchdog_s = _resolve_startup_stall_watchdog_seconds(1200)
|
|
1660
|
+
recovery_watchdog_s = _resolve_startup_stall_watchdog_seconds(
|
|
1661
|
+
1200,
|
|
1662
|
+
recovery_attempt=1,
|
|
1663
|
+
)
|
|
1664
|
+
|
|
1665
|
+
self.assertEqual(watchdog_s, 210)
|
|
1666
|
+
self.assertEqual(recovery_watchdog_s, 150)
|
|
1667
|
+
|
|
1668
|
+
def test_explicit_startup_stall_watchdog_override_is_bounded(self) -> None:
|
|
1669
|
+
with mock.patch.dict(
|
|
1670
|
+
os.environ,
|
|
1671
|
+
{"WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S": "500"},
|
|
1672
|
+
clear=False,
|
|
1673
|
+
):
|
|
1674
|
+
watchdog_s = _resolve_startup_stall_watchdog_seconds(120)
|
|
1675
|
+
|
|
1676
|
+
self.assertEqual(watchdog_s, 119)
|
|
1677
|
+
|
|
1590
1678
|
def test_narrow_contract_regression_with_required_e2e_uses_fast_no_edit_watchdog(self) -> None:
|
|
1591
1679
|
prompt = (
|
|
1592
1680
|
"Harden the opportunity graph contract around autonomous delivery-loop failure signals. "
|
|
@@ -155,14 +155,39 @@ def fail(summary: str, stderr: Optional[str] = None, exit_code: int = 1) -> int:
|
|
|
155
155
|
return exit_code
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def
|
|
159
|
-
|
|
160
|
-
payload = json.loads(decoded)
|
|
158
|
+
def _parse_payload_json(raw: str) -> Dict[str, Any]:
|
|
159
|
+
payload = json.loads(raw)
|
|
161
160
|
if not isinstance(payload, dict):
|
|
162
161
|
raise ValueError("payload must be a JSON object")
|
|
163
162
|
return payload
|
|
164
163
|
|
|
165
164
|
|
|
165
|
+
def decode_payload(raw: str) -> Dict[str, Any]:
|
|
166
|
+
stripped = str(raw or "").strip()
|
|
167
|
+
if not stripped:
|
|
168
|
+
raise ValueError("empty job payload")
|
|
169
|
+
|
|
170
|
+
# Direct workers normally receive a file-backed base64 payload, but this
|
|
171
|
+
# parser intentionally accepts the safe adjacent encodings too. That keeps
|
|
172
|
+
# executor startup resilient if an outer wrapper normalizes padding, uses
|
|
173
|
+
# url-safe base64, or hands through raw JSON during recovery.
|
|
174
|
+
if stripped.startswith("{"):
|
|
175
|
+
return _parse_payload_json(stripped)
|
|
176
|
+
|
|
177
|
+
compact = "".join(stripped.split())
|
|
178
|
+
padded = compact + ("=" * ((4 - len(compact) % 4) % 4))
|
|
179
|
+
decode_errors: List[str] = []
|
|
180
|
+
for decoder in (base64.b64decode, base64.urlsafe_b64decode):
|
|
181
|
+
try:
|
|
182
|
+
decoded = decoder(padded).decode("utf-8")
|
|
183
|
+
return _parse_payload_json(decoded)
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
decode_errors.append(str(exc))
|
|
186
|
+
|
|
187
|
+
detail = "; ".join(error for error in decode_errors if error) or "unknown decode error"
|
|
188
|
+
raise ValueError(f"invalid base64/JSON job payload: {detail}")
|
|
189
|
+
|
|
190
|
+
|
|
166
191
|
def read_encoded_payload_arg(argv: List[str]) -> str:
|
|
167
192
|
if len(argv) < 2:
|
|
168
193
|
raise ValueError("missing base64 job payload")
|
|
@@ -174,6 +199,13 @@ def read_encoded_payload_arg(argv: List[str]) -> str:
|
|
|
174
199
|
return path.read_text(encoding="utf-8").strip()
|
|
175
200
|
if mode == "--payload-stdin":
|
|
176
201
|
return sys.stdin.read().strip()
|
|
202
|
+
if len(mode) < 4096:
|
|
203
|
+
try:
|
|
204
|
+
path = Path(mode).expanduser()
|
|
205
|
+
if path.is_file():
|
|
206
|
+
return path.read_text(encoding="utf-8").strip()
|
|
207
|
+
except OSError:
|
|
208
|
+
pass
|
|
177
209
|
return mode
|
|
178
210
|
|
|
179
211
|
|
|
@@ -1918,8 +1918,6 @@ export class DockerExecutor {
|
|
|
1918
1918
|
|
|
1919
1919
|
private matchesRetryablePattern(text: string): boolean {
|
|
1920
1920
|
const transientPatterns: RegExp[] = [
|
|
1921
|
-
/\bstalled before first response\b/i,
|
|
1922
|
-
/\bstartup stall\b/i,
|
|
1923
1921
|
/warm .*runtime/i,
|
|
1924
1922
|
/failed to start warm container/i,
|
|
1925
1923
|
/docker execution error/i,
|