|
@@ -51,6 +51,9 @@ _DEFAULT_RESUME_FIRST_BYTE_TIMEOUT_S = 45.0
|
|
|
# resume 后主动开新会话,让 session 永远停在"小而快"的区间。0=不轮换。
|
|
# resume 后主动开新会话,让 session 永远停在"小而快"的区间。0=不轮换。
|
|
|
# 可配 config.extra["claude_session_rotate_after"]。
|
|
# 可配 config.extra["claude_session_rotate_after"]。
|
|
|
_DEFAULT_SESSION_ROTATE_AFTER = 8
|
|
_DEFAULT_SESSION_ROTATE_AFTER = 8
|
|
|
|
|
+# 连续多少次 _call_resume 整体首字节卡死后,判定该 session 本身太大/废了,
|
|
|
|
|
+# 强制清掉换新会话。保留会话只兜瞬时抖动;连卡说明是 session 大小问题,必须换。
|
|
|
|
|
+_DEFAULT_RESUME_STALL_GIVEUP = 2
|
|
|
|
|
|
|
|
|
|
|
|
|
class _StallError(Exception):
|
|
class _StallError(Exception):
|
|
@@ -328,6 +331,7 @@ class ClaudeCodeProvider(LLMProvider):
|
|
|
self.claude_bin = _find_working_claude(preferred) or preferred
|
|
self.claude_bin = _find_working_claude(preferred) or preferred
|
|
|
self._session_id = None
|
|
self._session_id = None
|
|
|
self._resume_count = 0 # 会话轮换计数(每 N 次 resume 换新会话,防大会话 resume 卡死)
|
|
self._resume_count = 0 # 会话轮换计数(每 N 次 resume 换新会话,防大会话 resume 卡死)
|
|
|
|
|
+ self._resume_stall_streak = 0 # 连续首字节卡死计数(熔断:连卡 K 次判 session 废了→清掉换新)
|
|
|
|
|
|
|
|
if not shutil.which(self.claude_bin) and not os.path.isfile(self.claude_bin):
|
|
if not shutil.which(self.claude_bin) and not os.path.isfile(self.claude_bin):
|
|
|
raise ProviderError(
|
|
raise ProviderError(
|
|
@@ -611,6 +615,25 @@ class ClaudeCodeProvider(LLMProvider):
|
|
|
# → 清掉,下次退化成 fresh first-turn 重启。
|
|
# → 清掉,下次退化成 fresh first-turn 重启。
|
|
|
if stall_exc.kind != "first-byte":
|
|
if stall_exc.kind != "first-byte":
|
|
|
self._session_id = None
|
|
self._session_id = None
|
|
|
|
|
+ self._resume_stall_streak = 0
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 首字节卡死本应保留会话(兜瞬时抖动)。但若**连续**多次
|
|
|
|
|
+ # _call_resume 都整体卡死,说明不是抖动而是这个 session 已
|
|
|
|
|
+ # 大到 resume 必卡 → 熔断:清掉会话,下次退化成 fresh
|
|
|
|
|
+ # first-turn(快)。否则会像那条 run 一样在同一大 session 上
|
|
|
|
|
+ # 连卡 8 次打转到死。
|
|
|
|
|
+ self._resume_stall_streak += 1
|
|
|
|
|
+ _giveup = int((self.config.extra or {}).get(
|
|
|
|
|
+ "claude_resume_stall_giveup", _DEFAULT_RESUME_STALL_GIVEUP))
|
|
|
|
|
+ if _giveup > 0 and self._resume_stall_streak >= _giveup:
|
|
|
|
|
+ logger.warning(
|
|
|
|
|
+ "claude-code resume 连续首字节卡死 %d 次 — 判定 session "
|
|
|
|
|
+ "太大/已废,清会话换新(下次走 fresh first-turn)",
|
|
|
|
|
+ self._resume_stall_streak,
|
|
|
|
|
+ )
|
|
|
|
|
+ self._session_id = None
|
|
|
|
|
+ self._resume_count = 0
|
|
|
|
|
+ self._resume_stall_streak = 0
|
|
|
raise ProviderError(
|
|
raise ProviderError(
|
|
|
f"Claude Code stalled ({stall_exc.kind}-timeout after "
|
|
f"Claude Code stalled ({stall_exc.kind}-timeout after "
|
|
|
f"{stall_exc.elapsed:.0f}s, no output) on resume",
|
|
f"{stall_exc.elapsed:.0f}s, no output) on resume",
|
|
@@ -622,6 +645,7 @@ class ClaudeCodeProvider(LLMProvider):
|
|
|
"claude-code resume ok (attempt %d): elapsed=%.1fs out_len=%d",
|
|
"claude-code resume ok (attempt %d): elapsed=%.1fs out_len=%d",
|
|
|
attempt, elapsed, len(result.stdout or ""),
|
|
attempt, elapsed, len(result.stdout or ""),
|
|
|
)
|
|
)
|
|
|
|
|
+ self._resume_stall_streak = 0 # 成功一次即清零连卡计数
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# Non-zero exit. Inspect for transport-error marker.
|
|
# Non-zero exit. Inspect for transport-error marker.
|