From 80227be746f02b60f6f87d05c00a9f85484a429e Mon Sep 17 00:00:00 2001 From: tybradle <78485586+tybradle@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:30:46 -0500 Subject: [PATCH] fix: resolve heartbeat race condition causing session stalls (#155) This fixes a race condition where the client watchdog timeout (25s) was shorter than the server heartbeat interval (30s). Changes: - Server: Decreased heartbeat interval from 30s to 15s to send keepalives more frequently. - Client: Increased stale connection threshold from 25s to 45s to allow for 3 missed heartbeats before reconnecting. This ensures stable connections during long subagent tasks where no text generation occurs. Co-authored-by: OpenCode Test --- packages/ui/src/hooks/useEventStream.ts | 2 +- packages/web/server/index.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/hooks/useEventStream.ts b/packages/ui/src/hooks/useEventStream.ts index 99f6de60..3d22c6c5 100644 --- a/packages/ui/src/hooks/useEventStream.ts +++ b/packages/ui/src/hooks/useEventStream.ts @@ -1675,7 +1675,7 @@ export const useEventStream = () => { if (hasBusySessions) { void refreshSessionActivityStatus(); } - if (now - lastEventTimestampRef.current > 25000) { + if (now - lastEventTimestampRef.current > 45000) { Promise.resolve().then(async () => { try { const healthy = await opencodeClient.checkHealth(); diff --git a/packages/web/server/index.js b/packages/web/server/index.js index 0ce030be..917ee994 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -15,7 +15,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const DEFAULT_PORT = 3000; -const HEALTH_CHECK_INTERVAL = 30000; +const HEALTH_CHECK_INTERVAL = 15000; const SHUTDOWN_TIMEOUT = 10000; const MODELS_DEV_API_URL = 'https://models.dev/api.json'; const MODELS_METADATA_CACHE_TTL = 5 * 60 * 1000; @@ -2244,7 +2244,7 @@ async function main(options = {}) { const heartbeatInterval = setInterval(() => { writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() }); - }, 30000); + }, 15000); const decoder = new TextDecoder(); const reader = upstream.body.getReader(); @@ -2370,7 +2370,7 @@ async function main(options = {}) { const heartbeatInterval = setInterval(() => { writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() }); - }, 30000); + }, 15000); const decoder = new TextDecoder(); const reader = upstream.body.getReader();