diff --git a/lib/ccbd/keeper_runtime/loop.py b/lib/ccbd/keeper_runtime/loop.py index d69dd8e96..61a1ede57 100644 --- a/lib/ccbd/keeper_runtime/loop.py +++ b/lib/ccbd/keeper_runtime/loop.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from pathlib import Path from agents.config_identity import project_config_identity_payload @@ -14,6 +15,23 @@ from .support import reap_child_processes, try_acquire_keeper_lock +def _keeper_ping_timeout_s() -> float: + """Keeper→ccbd config-check ping timeout. + + Upstream hardcodes 0.2s which spuriously trips when ccbd is busy with a + paste/poll cycle → lifecycle.failed → all ccb ask rejected. Env-override + lets ops raise it without patching upstream again. + """ + raw = os.environ.get('CCB_KEEPER_PING_TIMEOUT_S', '').strip() + if not raw: + return 2.0 + try: + value = float(raw) + except (TypeError, ValueError): + return 2.0 + return value if value > 0 else 2.0 + + def run_forever(app, *, poll_interval: float = 0.5, start_timeout_s: float = 5.0) -> int: lock_path = app.paths.ccbd_dir / 'keeper.lock' lock_handle = try_acquire_keeper_lock(lock_path) @@ -176,7 +194,7 @@ def stale_restart_state(app, *, state: KeeperState, inspection, occurred_at: str def daemon_matches_project_config(app) -> bool: expected = project_config_identity_payload(load_project_config(app.project_root).config) - payload = CcbdClient(app.paths.ccbd_socket_path, timeout_s=0.2).ping('ccbd') + payload = CcbdClient(app.paths.ccbd_socket_path, timeout_s=_keeper_ping_timeout_s()).ping('ccbd') actual_signature = str(payload.get('config_signature') or '').strip() if actual_signature: return actual_signature == expected['config_signature'] diff --git a/lib/runtime_env/control_plane.py b/lib/runtime_env/control_plane.py index 35b52d5f6..7dd39d400 100644 --- a/lib/runtime_env/control_plane.py +++ b/lib/runtime_env/control_plane.py @@ -11,6 +11,7 @@ 'CCB_CCBD_MIN_POLL_INTERVAL_S', 'CCB_DEBUG', 'CCB_KEEPER_PID', + 'CCB_KEEPER_PING_TIMEOUT_S', 'CCB_LANG', 'CCB_NO_ATTACH', 'CCB_REPLY_LANG',