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 <test@opencode.ai>
This commit is contained in:
tybradle
2026-01-15 22:30:46 +02:00
committed by GitHub
co-authored by OpenCode Test
parent 1be5dfda05
commit 80227be746
2 changed files with 4 additions and 4 deletions
+1 -1
View File
@@ -1675,7 +1675,7 @@ export const useEventStream = () => {
if (hasBusySessions) { if (hasBusySessions) {
void refreshSessionActivityStatus(); void refreshSessionActivityStatus();
} }
if (now - lastEventTimestampRef.current > 25000) { if (now - lastEventTimestampRef.current > 45000) {
Promise.resolve().then(async () => { Promise.resolve().then(async () => {
try { try {
const healthy = await opencodeClient.checkHealth(); const healthy = await opencodeClient.checkHealth();
+3 -3
View File
@@ -15,7 +15,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const DEFAULT_PORT = 3000; const DEFAULT_PORT = 3000;
const HEALTH_CHECK_INTERVAL = 30000; const HEALTH_CHECK_INTERVAL = 15000;
const SHUTDOWN_TIMEOUT = 10000; const SHUTDOWN_TIMEOUT = 10000;
const MODELS_DEV_API_URL = 'https://models.dev/api.json'; const MODELS_DEV_API_URL = 'https://models.dev/api.json';
const MODELS_METADATA_CACHE_TTL = 5 * 60 * 1000; const MODELS_METADATA_CACHE_TTL = 5 * 60 * 1000;
@@ -2244,7 +2244,7 @@ async function main(options = {}) {
const heartbeatInterval = setInterval(() => { const heartbeatInterval = setInterval(() => {
writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() }); writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() });
}, 30000); }, 15000);
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const reader = upstream.body.getReader(); const reader = upstream.body.getReader();
@@ -2370,7 +2370,7 @@ async function main(options = {}) {
const heartbeatInterval = setInterval(() => { const heartbeatInterval = setInterval(() => {
writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() }); writeSseEvent(res, { type: 'openchamber:heartbeat', timestamp: Date.now() });
}, 30000); }, 15000);
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const reader = upstream.body.getReader(); const reader = upstream.body.getReader();